1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51:
52: class Horde_Rpc_Webdav extends Horde_Rpc
53: {
54: 55: 56: 57: 58:
59: const CALDAVNS = 'urn:ietf:params:xml:ns:caldav';
60:
61: 62: 63: 64: 65:
66: var $http_auth_realm = 'Horde WebDAV';
67:
68: 69: 70: 71: 72:
73: var $dav_powered_by = 'Horde WebDAV Server';
74:
75: 76: 77: 78: 79: 80:
81: var $parseSuccess = false;
82:
83: 84: 85: 86: 87: 88:
89: var $parseProps = false;
90:
91: 92: 93: 94: 95: 96:
97: var $parseDepth = 0;
98:
99: 100: 101: 102: 103: 104:
105: var $locktype = "";
106:
107: 108: 109: 110: 111: 112:
113: var $lockscope = "";
114:
115: 116: 117: 118: 119: 120:
121: var $owner = "";
122:
123: 124: 125: 126: 127: 128:
129: var $collect_owner = false;
130:
131: 132: 133: 134: 135: 136:
137: var $mode;
138:
139: 140: 141: 142: 143: 144:
145: var $current;
146:
147: 148: 149: 150: 151:
152: var $uri;
153:
154:
155: 156: 157: 158: 159:
160: var $base_uri;
161:
162:
163: 164: 165: 166: 167:
168: var $path;
169:
170: 171: 172: 173: 174:
175: var = array();
176:
177: 178: 179: 180: 181:
182: var $_http_status = "200 OK";
183:
184: 185: 186: 187: 188: 189: 190: 191:
192: var $_SERVER;
193:
194: 195: 196: 197: 198:
199: var $ns_hash = array('DAV:' => 'D');
200:
201: 202: 203: 204:
205: var $_xml;
206:
207: 208: 209: 210: 211:
212: public function __construct($request, $params = array())
213: {
214:
215: ini_set('display_errors', 0);
216:
217:
218:
219: $this->_SERVER = $_SERVER;
220:
221: parent::__construct($request, $params);
222: }
223:
224: 225: 226: 227:
228: function authorize()
229: {
230: return true;
231: }
232:
233: 234: 235: 236:
237: function getInput()
238: {
239: }
240:
241: 242: 243: 244: 245: 246: 247:
248: function getResponse($request)
249: {
250: $this->ServeRequest();
251: exit;
252: }
253:
254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266:
267: function GET(&$options)
268: {
269: if ($options['path'] == '/') {
270: $options['mimetype'] = 'httpd/unix-directory';
271: } else {
272:
273: $options['depth'] = 0;
274: try {
275: $result = $this->_list($options, false);
276: if ($result === false) {
277: return '404 File Not Found';
278: }
279: } catch (Horde_Rpc_Exception $e) {
280: if ($e->getCode()) {
281:
282: return $this->_checkHTTPCode($e->getCode())
283: . ' ' . $e->getMessage();
284: }
285: return '500 Internal Server Error';
286: }
287: $options = $result;
288: }
289:
290: return true;
291: }
292:
293: 294: 295: 296: 297: 298: 299:
300: function PUT(&$options)
301: {
302: $path = trim($options['path'], '/');
303:
304: if (empty($path)) {
305: return '403 PUT requires a path.';
306: }
307:
308: $pieces = explode('/', $path);
309:
310: if (count($pieces) < 2 || empty($pieces[0])) {
311: return '403 PUT denied outside of application directories.';
312: }
313:
314: $content = '';
315: while (!feof($options['stream'])) {
316: $content .= fgets($options['stream']);
317: }
318:
319: try {
320: $GLOBALS['registry']->callByPackage($pieces[0], 'put', array('path' => $path, 'content' => $content, 'type' => $options['content_type']));
321: } catch (Horde_Exception $e) {
322: Horde::logMessage($e, 'ERR');
323: if ($e->getCode()) {
324: return $this->_checkHTTPCode($e->getCode()) . ' ' . $result->getMessage();
325: }
326:
327: return '500 Internal Server Error. Check server logs';
328: }
329:
330: return true;
331: }
332:
333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345:
346: function DELETE($options)
347: {
348: $path = $options['path'];
349: $pieces = explode('/', trim($this->path, '/'), 2);
350:
351: if (count($pieces) != 2) {
352: Horde::logMessage(sprintf(Horde_Rpc_Translation::t("Error deleting from path %s; must be [app]/[path]", $options['path'])), 'INFO');
353: return '403 Must supply a resource within the application to delete.';
354: }
355:
356: $app = $pieces[0];
357: $path = $pieces[1];
358:
359:
360:
361:
362: try {
363: $GLOBALS['registry']->callByPackage($app, 'path_delete', array($path));
364: } catch (Horde_Exception $e) {
365: Horde::logMessage($e, 'INFO');
366: if ($e->getCode()) {
367: return $this->_checkHTTPCode($e->getCode()) . ' ' . $e->getMessage();
368: }
369:
370: return '500 Internal Server Error. Check server logs';
371: }
372:
373: return '204 No Content';
374: }
375:
376: 377: 378: 379: 380: 381: 382: 383:
384: function PROPFIND($options, &$files)
385: {
386:
387:
388:
389: try {
390: $list = $this->_list($options, true);
391: if ($list === false) {
392: return false;
393: }
394: } catch (Horde_Rpc_Exception $e) {
395: return false;
396: }
397: $files['files'] = $list;
398: return true;
399: }
400:
401: 402: 403: 404: 405: 406:
407: function MKCOL($options)
408: {
409: $path = $options['path'];
410: if (substr($path, 0, 1) == '/') {
411: $path = substr($path, 1);
412: }
413:
414:
415: $pieces = explode('/', $path, 2);
416: if (count($pieces) != 2) {
417: Horde::logMessage(sprintf(Horde_Rpc_Translation::t("Unable to create directory %s; must be [app]/[path]"), $path), 'INFO');
418: return '403 Must specify a resource within an application. MKCOL disallowed at top level.';
419: }
420:
421:
422: try {
423: $GLOBALS['registry']->callByPackage($pieces[0], 'mkcol', array('path' => $path));
424: } catch (Horde_Exception $e) {
425: Horde::logMessage($result, 'ERR');
426: if ($e->getCode()) {
427: return $this->_checkHTTPCode($e->getCode()) . ' ' . $e->getMessage();
428: }
429:
430: return '500 Internal Server Error. Check server logs';
431: }
432:
433: return '200 OK';
434: }
435:
436: 437: 438: 439: 440: 441:
442: function MOVE($options)
443: {
444: $path = $options['path'];
445: if (substr($path, 0, 1) == '/') {
446: $path = substr($path, 1);
447: }
448:
449:
450: $sourcePieces = explode('/', $path, 2);
451: if (count($sourcePieces) != 2) {
452: Horde::logMessage(sprintf(Horde_Rpc_Translation::t("Unable to rename %s; must be [app]/[path] and within the same application."), $path), 'INFO');
453: return '403 Must specify a resource within an application. MOVE disallowed at top level.';
454: }
455:
456: $destPieces = explode('/', $options['dest'], 2);
457: if (!(count($destPieces) == 2) || $sourcesPieces[0] != $destPieces[0]) {
458: return '400 Can not move across applications.';
459: }
460:
461:
462: try {
463: $GLOBALS['registry']->callByPackage($sourcePieces[0], 'move', array('path' => $path, 'dest' => $options['dest']));
464: } catch (Horde_Exception $e) {
465: Horde::logMessage($e, 'ERR');
466: if ($e->getCode()) {
467: return $this->_checkHTTPCode($e->getCode()) . ' ' . $e->getMessage();
468: }
469:
470: return '500 Internal Server Error. Check server logs';
471: }
472:
473: return '200 OK';
474: }
475:
476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487:
488: function _list($options, $properties)
489: {
490: global $registry;
491:
492:
493:
494:
495: $path = $options['path'];
496: $depth = $options['depth'];
497:
498:
499: $list = array();
500:
501: if ($path == '/') {
502:
503:
504: $now = time();
505: $root = array('name' => '/',
506: 'created' => $now,
507: 'modified' => $now,
508: 'contenttype' => 'httpd/unix-directory',
509: 'contentlength' => 0,
510: 'browseable' => true);
511: $list[] = array('path' => $path,
512: 'props' => $this->_getProps($options['props'], $root));
513:
514: try {
515: $apps = $registry->listApps(null, false, Horde_Perms::READ);
516: } catch (Horde_Exception $e) {
517: Horde::logMessage($e);
518: throw new Horde_Rpc_Exception($e);
519: }
520: foreach ($apps as $app) {
521:
522: if ($registry->hasMethod('browse', $app)) {
523: $list[] = array('path' => '/' . $app,
524: 'props' => $this->_getProps($options['props'], array_merge($root, array('name' => $registry->get('name', $app)))));
525: }
526: }
527: return $list;
528: } else {
529: $path = trim($path, '/');
530: $pieces = explode('/', $path);
531:
532: try {
533: $items = $registry->callByPackage($pieces[0], 'browse', array('path' => $path, 'properties' => array('name', 'browseable', 'contenttype', 'contentlength', 'created', 'modified')));
534: } catch (Horde_Exception $e) {
535: Horde::logMessage($e);
536: throw new Horde_Rpc_Exception($e);
537: }
538:
539: if ($items === false) {
540:
541: return $items;
542: }
543: if (empty($items)) {
544:
545: return array();
546: }
547: if (!is_array(reset($items))) {
548: 549:
550: if ($properties) {
551: $props = $this->_getProps($options['props'], $items);
552: $items = array(array('path' => $this->path,
553: 'props' => $props));
554: }
555: return $items;
556: }
557:
558:
559: foreach ($items as $sub_path => $i) {
560: $props = $this->_getProps($options['props'], $i);
561:
562: $item = array('path' => '/' . $sub_path,
563: 'props' => $props);
564: $list[] = $item;
565: }
566: }
567:
568: return $list;
569: }
570:
571: 572: 573: 574: 575: 576: 577: 578: 579: 580:
581: function _getProps($reqprops, $item)
582: {
583: $props = array();
584: $properties = array();
585: foreach ($reqprops as $prop) {
586: if (!isset($properties[$prop['xmlns']])) {
587: $properties[$prop['xmlns']] = array();
588: }
589: $properties[$prop['xmlns']][$prop['name']] = $prop['name'];
590: }
591:
592:
593: if (in_array('displayname', $properties['DAV:'])) {
594: $props[] = $this->mkprop('displayname', $item['name']);
595: unset($properties['DAV:']['displayname']);
596: }
597: if (in_array('resourcetype', $properties['DAV:'])) {
598: $props[] = $this->mkprop('resourcetype', $item['browseable'] ? 'collection' : '');
599: unset($properties['DAV:']['resourcetype']);
600: }
601: if (in_array('getcontenttype', $properties['DAV:'])) {
602: $props[] = $this->mkprop('getcontenttype', empty($item['contenttype']) ? 'application/octet-stream' : $item['contenttype']);
603: unset($properties['DAV:']['getcontenttype']);
604: }
605: if (in_array('getcontentlength', $properties['DAV:'])) {
606: if (empty($item['contentlength']) && empty($item['data'])) {
607: $size = 0;
608: } else {
609: $size = empty($item['contentlength']) ? strlen($item['data']) : $item['contentlength'];
610: }
611: $props[] = $this->mkprop('getcontentlength', $size);
612: unset($properties['DAV:']['getcontentlength']);
613: }
614: if (in_array('creationdate', $properties['DAV:'])) {
615: $props[] = $this->mkprop('creationdate', empty($item['created']) ? time() : $item['created']);
616: unset($properties['DAV:']['creationdate']);
617: }
618: if (in_array('getlastmodified', $properties['DAV:'])) {
619: $props[] = $this->mkprop('getlastmodified', empty($item['modified']) ? time() : $item['modified']);
620: unset($properties['DAV:']['getlastmodified']);
621: }
622:
623: if (isset($properties[self::CALDAVNS])) {
624: if (in_array('calendar-home-set', $properties[self::CALDAVNS]) &&
625: isset($item[self::CALDAVNS . ':calendar-home-set'])) {
626: $calendar_home_set = array();
627: foreach ($item[self::CALDAVNS . ':calendar-home-set'] as $calUrl) {
628: $calendar_home_set[] = $this->mkprop('href', $calUrl);
629: }
630: $props[] = $this->mkprop('caldav', 'calendar-home-set', $calendar_home_set);
631: unset($properties[self::CALDAVNS]['calendar-home-set']);
632: }
633:
634: if (in_array('calendar-user-address-set', $properties[self::CALDAVNS]) &&
635: isset($item[self::CALDAVNS . ':calendar-user-address-set'])) {
636: $calendar_user_address_set = array();
637: foreach ($item[self::CALDAVNS . ':calendar-user-address-set'] as $userAddress) {
638: $calendar_user_address_set[] = $this->mkprop('href', $userAddress);
639: }
640: $props[] = $this->mkprop('caldav', 'calendar-user-address-set', $calendar_user_address_set);
641: unset($properties[self::CALDAVNS]['calendar-user-address-set']);
642: }
643: }
644:
645:
646: $itemprops = array_keys($item);
647: foreach (array_keys($properties) as $xmlns) {
648: foreach ($properties[$xmlns] as $propname) {
649: if ($xmlns != 'DAV:') {
650: $propname = $xmlns . ':' . $propname;
651: }
652: if (in_array($propname, $itemprops)) {
653: $props[] = $this->mkprop($xmlns, $propname, $item[$propname]);
654: }
655: }
656: }
657:
658: return $props;
659: }
660:
661: 662: 663: 664: 665: 666: 667: 668: 669:
670: function LOCK(&$params)
671: {
672: if (!isset($GLOBALS['conf']['lock']['driver']) ||
673: $GLOBALS['conf']['lock']['driver'] == 'none') {
674: return 500;
675: }
676:
677: if (empty($params['path'])) {
678: Horde::logMessage('Empty path supplied to LOCK()', 'ERR');
679: return 403;
680: }
681: if ($params['path'] == '/') {
682:
683: return 403;
684: }
685: if (isset($params['depth']) && $params['depth'] == 'infinity') {
686:
687: return 403;
688: }
689:
690: if (!is_array($params['timeout']) || count($params['timeout']) != 1) {
691:
692: $timeout = 600;
693: }
694: $tmp = explode('-', $params['timeout'][0]);
695: if (count($tmp) != 2) {
696:
697: $timeout = 600;
698: }
699: if (strtolower($tmp[0]) == 'second') {
700: $timeout = $tmp[1];
701: } else {
702:
703: $timeout = 600;
704: }
705:
706: try {
707: $locks = $GLOBALS['injector']->getInstance('Horde_Lock');
708: } catch (Horde_Lock_Exception $e) {
709: Horde::logMessage($e, 'ERR');
710: return 500;
711: }
712:
713: $locktype = Horde_Lock::TYPE_SHARED;
714: if ($params['scope'] == 'exclusive') {
715: $locktype = Horde_Lock::TYPE_EXCLUSIVE;
716: }
717:
718: try {
719: $lockid = $locks->setLock($GLOBALS['registry']->getAuth(), 'webdav', $params['path'],
720: $timeout, $locktype);
721: } catch (Horde_Lock_Exception $e) {
722: Horde::logMessage($e, 'ERR');
723: return 500;
724: }
725: if ($lockid === false) {
726:
727: return 423;
728: }
729:
730: $params['locktoken'] = $lockid;
731: $params['owner'] = $GLOBALS['registry']->getAuth();
732: $params['timeout'] = $timeout;
733:
734: return "200";
735: }
736:
737: 738: 739: 740: 741: 742: 743: 744: 745:
746: function UNLOCK(&$params)
747: {
748: if (!isset($GLOBALS['conf']['lock']['driver']) ||
749: $GLOBALS['conf']['lock']['driver'] == 'none') {
750: return 500;
751: }
752:
753: try {
754: $locks = $GLOBALS['injector']->getInstance('Horde_Lock');
755: } catch (Horde_Lock_Exception $e) {
756: Horde::logMessage($e, 'ERR');
757: return 500;
758: }
759:
760: try {
761: $res = $locks->clearLock($params['token']);
762: } catch (Horde_Lock_Exception $e) {
763: Horde::logMessage($e, 'ERR');
764: return 500;
765: }
766: if ($res === false) {
767: Horde::logMessage('clearLock() returned false', 'ERR');
768:
769: return 424;
770: }
771:
772:
773:
774: return 204;
775: }
776:
777: function checkLock($resource)
778: {
779: if (!isset($GLOBALS['conf']['lock']['driver']) ||
780: $GLOBALS['conf']['lock']['driver'] == 'none') {
781: Horde::logMessage('WebDAV locking failed because no lock driver has been configured.', 'WARN');
782: return false;
783: }
784:
785: try {
786: $locks = $GLOBALS['injector']->getInstance('Horde_Lock');
787: } catch (Horde_Lock_Exception $e) {
788: Horde::logMessage($e, 'ERR');
789: return false;
790: }
791:
792: try {
793: $res = $locks->getLocks('webdav', $resource);
794: } catch (Horde_Lock_Exception $e) {
795: Horde::logMessage($e, 'ERR');
796: return false;
797: }
798:
799: if (empty($res)) {
800:
801: return $res;
802: }
803:
804:
805: $lock = reset($res);
806:
807:
808: $ret = array();
809: if ($lock['lock_type'] == Horde_Lock::TYPE_EXCLUSIVE) {
810: $ret['scope'] = 'exclusive';
811: } else {
812: $ret['scope'] = 'shared';
813: }
814: $ret['type'] = 'write';
815: $ret['expires'] = $lock['lock_expiry_timestamp'];
816: $ret['token'] = $lock['lock_id'];
817: $ret['depth'] = 1;
818:
819: return $ret;
820: }
821:
822: 823: 824: 825: 826: 827: 828: 829: 830: 831: 832:
833: function check_auth($type, $username, $password)
834: {
835: $auth = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Auth')->create();
836: return $auth->authenticate($username, array('password' => $password));
837: }
838:
839: 840: 841: 842: 843: 844: 845: 846: 847: 848: 849: 850: 851: 852: 853: 854: 855:
856: function _checkHTTPcode($code)
857: {
858: $valid = array(200,
859: 201,
860: 202,
861: 204,
862: 301,
863: 302,
864: 304,
865: 307,
866: 400,
867: 401,
868: 403,
869: 404,
870: 405,
871: 406,
872: 408,
873: 413,
874: 415,
875: 500,
876: 501,
877: 503,
878: );
879: if (in_array($code, $valid)) {
880: return $code;
881: } else {
882: return 500;
883: }
884: }
885:
886: 887: 888: 889: 890: 891: 892: 893:
894: function ServeRequest()
895: {
896:
897: if (strstr($this->_SERVER["REQUEST_URI"], '#')) {
898: $this->http_status("400 Bad Request");
899: return;
900: }
901:
902:
903: $script_name = preg_replace('/index.php$/', '', $this->_SERVER["SCRIPT_NAME"]);
904: $uri = "http";
905: if (isset($this->_SERVER["HTTPS"]) && $this->_SERVER["HTTPS"] === "on") {
906: $uri = "https";
907: }
908: $uri.= "://".$this->_SERVER["HTTP_HOST"].$script_name;
909:
910:
911:
912:
913: $path_info = substr($this->_SERVER["REQUEST_URI"], strlen($script_name));
914:
915:
916: if (empty($path_info)) {
917: $path_info = "/";
918: }
919:
920: $this->uri = $this->base_uri = $uri;
921: if (substr($uri, -1) == '/') {
922: $this->uri = substr($this->uri, 0, -1);
923: }
924: $this->uri .= $path_info;
925:
926:
927: $this->path = $this->_urldecode($path_info);
928: if (!strlen($this->path)) {
929: if ($this->_SERVER["REQUEST_METHOD"] == "GET") {
930:
931:
932:
933: header("Location: ".$this->base_uri."/");
934: return;
935: } else {
936:
937: $this->path = "/";
938: }
939: }
940:
941: if (ini_get("magic_quotes_gpc")) {
942: $this->path = stripslashes($this->path);
943: }
944:
945:
946:
947: if (empty($this->dav_powered_by)) {
948: header("X-Dav-Powered-By: PHP class: ".get_class($this));
949: } else {
950: header("X-Dav-Powered-By: ".$this->dav_powered_by);
951: }
952:
953:
954:
955:
956: if ( ( !(($this->_SERVER['REQUEST_METHOD'] == 'OPTIONS') && ($this->path == "/")))
957: && (!$this->_check_auth())) {
958:
959:
960:
961:
962: header('WWW-Authenticate: Basic realm="'.($this->http_auth_realm).'"');
963:
964:
965:
966: $this->http_status('401 Unauthorized');
967:
968: return;
969: }
970:
971:
972: if (! $this->_check_if_header_conditions()) {
973: return;
974: }
975:
976:
977: $method = strtolower($this->_SERVER["REQUEST_METHOD"]);
978: $wrapper = "http_".$method;
979:
980:
981: if ($method == "head" && !method_exists($this, "head")) {
982: $method = "get";
983: }
984:
985: if (method_exists($this, $wrapper) && ($method == "options" || method_exists($this, $method))) {
986: $this->$wrapper();
987: } else {
988: if ($this->_SERVER["REQUEST_METHOD"] == "LOCK") {
989: $this->http_status("412 Precondition failed");
990: } else {
991: $this->http_status("405 Method not allowed");
992: header("Allow: ".join(", ", $this->_allow()));
993: }
994: }
995: }
996:
997:
998:
999:
1000:
1001:
1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018:
1019:
1020: 1021: 1022: 1023: 1024: 1025:
1026:
1027:
1028:
1029:
1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038:
1039:
1040: 1041: 1042: 1043: 1044: 1045:
1046:
1047:
1048:
1049:
1050:
1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059:
1060:
1061: 1062: 1063: 1064: 1065: 1066:
1067:
1068:
1069:
1070:
1071:
1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080:
1081:
1082: 1083: 1084: 1085: 1086: 1087:
1088:
1089:
1090:
1091:
1092:
1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101:
1102:
1103: 1104: 1105: 1106: 1107: 1108:
1109:
1110:
1111:
1112:
1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121:
1122:
1123: 1124: 1125: 1126: 1127: 1128:
1129:
1130:
1131:
1132:
1133:
1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142:
1143:
1144: 1145: 1146: 1147: 1148: 1149:
1150:
1151:
1152:
1153:
1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162:
1163:
1164: 1165: 1166: 1167: 1168: 1169:
1170:
1171:
1172:
1173:
1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182:
1183:
1184: 1185: 1186: 1187: 1188: 1189:
1190:
1191:
1192:
1193:
1194:
1195:
1196:
1197:
1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208:
1209:
1210: 1211: 1212: 1213: 1214: 1215:
1216:
1217:
1218:
1219:
1220:
1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230: 1231:
1232:
1233: 1234: 1235: 1236: 1237: 1238:
1239:
1240:
1241:
1242:
1243:
1244:
1245:
1246:
1247:
1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257:
1258: function http_OPTIONS()
1259: {
1260:
1261:
1262: header("MS-Author-Via: DAV");
1263:
1264:
1265: $allow = $this->_allow();
1266:
1267:
1268: $dav = array(1);
1269: if (isset($allow['LOCK'])) {
1270: $dav[] = 2;
1271: }
1272:
1273:
1274: $this->http_status("200 OK");
1275: header("DAV: " .join(", ", $dav));
1276: header("Allow: ".join(", ", $allow));
1277:
1278: header("Content-length: 0");
1279: }
1280:
1281:
1282:
1283:
1284:
1285:
1286: 1287: 1288: 1289: 1290: 1291:
1292: function http_PROPFIND()
1293: {
1294: $options = Array();
1295: $files = Array();
1296:
1297: $options["path"] = $this->path;
1298:
1299:
1300: if (isset($this->_SERVER['HTTP_DEPTH'])) {
1301: $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
1302: } else {
1303: $options["depth"] = "infinity";
1304: }
1305:
1306:
1307: $propinfo = $this->_parse_propfind("php://input");
1308: if (!$this->parseSuccess) {
1309: $this->http_status("400 Error");
1310: return;
1311: }
1312: $options['props'] = $this->parseProps;
1313:
1314:
1315: if (!$this->PROPFIND($options, $files)) {
1316: $files = array("files" => array());
1317: if (method_exists($this, "checkLock")) {
1318:
1319: $lock = $this->checkLock($this->path);
1320:
1321: if (is_array($lock) && count($lock)) {
1322: $created = isset($lock['created']) ? $lock['created'] : time();
1323: $modified = isset($lock['modified']) ? $lock['modified'] : time();
1324: $files['files'][] = array("path" => $this->_slashify($this->path),
1325: "props" => array($this->mkprop("displayname", $this->path),
1326: $this->mkprop("creationdate", $created),
1327: $this->mkprop("getlastmodified", $modified),
1328: $this->mkprop("resourcetype", ""),
1329: $this->mkprop("getcontenttype", ""),
1330: $this->mkprop("getcontentlength", 0))
1331: );
1332: }
1333: }
1334:
1335: if (empty($files['files'])) {
1336: $this->http_status("404 Not Found");
1337: return;
1338: }
1339: }
1340:
1341: $this->_xml = new Horde_Xml_Element('<D:multistatus xmlns:D="DAV:"/>');
1342: $this->_xml->registerNamespace('D', "DAV:");
1343: $this->_xml->registerNamespace('caldav', self::CALDAVNS);
1344:
1345:
1346:
1347:
1348:
1349: foreach ($files["files"] as $filekey => $file) {
1350:
1351:
1352: if (!isset($file["props"]) || !is_array($file["props"])) {
1353: continue;
1354: }
1355:
1356:
1357: foreach ($file["props"] as $key => $prop) {
1358:
1359:
1360:
1361:
1362: switch($options['props']) {
1363: case "all":
1364:
1365: break;
1366:
1367: case "names":
1368:
1369:
1370: unset($files["files"][$filekey]["props"][$key]["val"]);
1371: break;
1372:
1373: default:
1374: $found = false;
1375:
1376:
1377: foreach ((array)$options["props"] as $reqprop) {
1378: if (!isset($reqprop["xmlns"])) {
1379: $reqprop["xmlns"] = "";
1380: }
1381: if ( $reqprop["name"] == $prop["name"]
1382: && $reqprop["xmlns"] == $prop["ns"]) {
1383: $found = true;
1384: break;
1385: }
1386: }
1387:
1388:
1389: if (!$found) {
1390: $files["files"][$filekey]["props"][$key]="";
1391: continue(2);
1392: }
1393: break;
1394: }
1395:
1396:
1397: if (empty($prop["ns"])) continue;
1398: $ns = $prop["ns"];
1399: if ($ns == "DAV:") continue;
1400: if (isset($this->ns_hash[$ns])) continue;
1401:
1402:
1403: $ns_name = "ns".(count($this->ns_hash));
1404: $this->ns_hash[$ns] = $ns_name;
1405: $this->_xml->registerNamespace($ns_name, $ns);
1406: }
1407:
1408:
1409:
1410: if (is_array($options['props'])) {
1411: foreach ($options["props"] as $reqprop) {
1412: if ($reqprop['name']=="") continue;
1413:
1414: $found = false;
1415:
1416: if (!isset($reqprop["xmlns"])) {
1417: $reqprop["xmlns"] = "";
1418: }
1419:
1420:
1421: foreach ($file["props"] as $prop) {
1422: if ( $reqprop["name"] == $prop["name"]
1423: && $reqprop["xmlns"] == $prop["ns"]) {
1424: $found = true;
1425: break;
1426: }
1427: }
1428:
1429: if (!$found) {
1430: if ($reqprop["xmlns"]==="DAV:" && $reqprop["name"]==="lockdiscovery") {
1431:
1432: $files["files"][$filekey]["props"][]
1433: = $this->mkprop("DAV:",
1434: "lockdiscovery",
1435: $this->lockdiscovery($files["files"][$filekey]['path']));
1436: } else {
1437:
1438: $files["files"][$filekey]["noprops"][] =
1439: $this->mkprop($reqprop["xmlns"], $reqprop["name"], "");
1440:
1441:
1442: if ($reqprop["xmlns"] != "DAV:" && !isset($this->ns_hash[$reqprop["xmlns"]])) {
1443: $ns_name = "ns".(count($this->ns_hash));
1444: $this->ns_hash[$reqprop["xmlns"]] = $ns_name;
1445: $this->_xml->registerNamespace($ns_name, $reqprop['xmlns']);
1446: }
1447: }
1448: }
1449: }
1450: }
1451: }
1452:
1453:
1454: $this->http_status("207 Multi-Status");
1455: header('Content-Type: text/xml; charset="utf-8"');
1456:
1457:
1458: foreach ($files["files"] as $file) {
1459:
1460: if (!is_array($file) || empty($file) || !isset($file["path"])) continue;
1461: $path = $file['path'];
1462: if (!is_string($path) || $path==="") continue;
1463:
1464: $xmldata = array('D:response' => array());
1465:
1466:
1467: 1468: 1469:
1470: $href = $this->_mergePaths($this->base_uri, $path);
1471:
1472:
1473: $xmldata['D:response']['D:href'] = $this->_urlencode($href);
1474:
1475:
1476:
1477: if (isset($file["props"]) && is_array($file["props"])) {
1478:
1479: $i = 0;
1480: $propstats = array($i => array('D:prop' => array()));
1481:
1482:
1483: foreach ($file["props"] as $key => $prop) {
1484:
1485: if (!is_array($prop)) continue;
1486: if (!isset($prop["name"])) continue;
1487:
1488: if (!isset($prop["val"]) || $prop["val"] === "" || $prop["val"] === false) {
1489:
1490: if ($prop["ns"]=="DAV:") {
1491: $propstats[$i]['D:prop']['D:' . $prop['name']] = '';
1492:
1493: } else if (!empty($prop["ns"])) {
1494: $propstats[$i]['D:prop'][$this->ns_hash[$prop["ns"]].':'.$prop['name']] = '';
1495:
1496: } else {
1497: $propstats[$i]['D:prop'][$prop['name']] = '';
1498: $propstats[$i]['D:prop'][$prop['name'] . '#xmlns'] = '';
1499:
1500: }
1501: } else if ($prop["ns"] == "DAV:") {
1502:
1503: switch ($prop["name"]) {
1504: case "creationdate":
1505: $propstats[$i]['D:prop']['D:creationdate'] = gmdate("Y-m-d\\TH:i:s\\Z", $prop['val']);
1506: $propstats[$i]['D:prop']['D:creationdate#xmldata:dt'] = 'dateTime.tz';
1507:
1508:
1509:
1510: break;
1511: case "getlastmodified":
1512: $propstats[$i]['D:prop']['D:getlastmodified'] = gmdate("D, d M Y H:i:s ", $prop['val']);
1513: $propstats[$i]['D:prop']['D:getlastmodified#xmldata:dt'] = 'dateTime.rfc1123';
1514:
1515:
1516:
1517: break;
1518: case "resourcetype":
1519: $propstats[$i]['D:prop']['D:resourcetype']['D:'.$prop['val']] = '';
1520:
1521: break;
1522: case "supportedlock":
1523: $propstats[$i]['D:prop']['D:supportedlock'] = $prop['val'];
1524:
1525: break;
1526: case "lockdiscovery":
1527: $propstats[$i]['D:prop']['D:lockdiscovery'] = $prop['val'];
1528:
1529:
1530:
1531: break;
1532:
1533: case "lastaccessed":
1534: $propstats[$i]['D:prop']['D:lastaccessed'] = gmdate("D, d M Y H:i:s ", $prop['val']);
1535: $propstats[$i]['D:prop']['D:lastaccessed#xmldata:dt'] = 'dateTime.rfc1123';
1536:
1537:
1538:
1539: break;
1540: case "ishidden":
1541: $propstats[$i]['D:prop']['D:ishidden'] = is_string($prop['val']) ? $prop['val'] : ($prop['val'] ? 'true' : 'false');
1542:
1543:
1544:
1545: break;
1546: default:
1547: $propstats[$i]['D:prop']['D:'. $prop['name']] = $prop['val'];
1548:
1549:
1550:
1551: break;
1552: }
1553: } else {
1554: list($key, $val) = $this->_prop2xml($prop);
1555: $propstats[$i]['D:prop'][$key] = $val;
1556:
1557: }
1558: }
1559:
1560:
1561: $propstats[$i]['D:status'] = 'HTTP/1.1 200 OK';
1562:
1563:
1564: }
1565:
1566: $i++;
1567:
1568:
1569: if (isset($file["noprops"])) {
1570:
1571: $propstats[$i]['D:prop'] = array();
1572:
1573:
1574: foreach ($file["noprops"] as $key => $prop) {
1575: if ($prop["ns"] == "DAV:") {
1576: $propstats[$i]['D:prop']['D:' . $prop['name']] = '';
1577:
1578: } else if ($prop["ns"] == "") {
1579: $propstats[$i]['D:prop'][$prop['name']] = '';
1580: $propstats[$i]['D:prop'][$prop['name'] . '#xmlns'] = '';
1581:
1582: } else {
1583: $propstats[$i]['D:prop'][$this->ns_hash[$prop['ns']] . ':' . $prop['name']] = '';
1584:
1585: }
1586: }
1587:
1588:
1589: $propstats[$i]['D:status'] = 'HTTP/1.1 404 Not Found';
1590:
1591:
1592: }
1593:
1594: $xmldata['D:response']['D:propstat'] = $propstats;
1595:
1596: $this->_xml->fromArray($xmldata);
1597: }
1598:
1599:
1600: echo $this->_xml->saveXml();
1601: }
1602:
1603:
1604:
1605:
1606:
1607:
1608: 1609: 1610: 1611: 1612: 1613:
1614: function http_PROPPATCH()
1615: {
1616: if ($this->_check_lock_status($this->path)) {
1617: $options = Array();
1618:
1619: $options["path"] = $this->path;
1620:
1621: $propinfo = $this->_parse_proppatch("php://input");
1622:
1623: if (!$this->parseSuccess) {
1624: $this->http_status("400 Error");
1625: return;
1626: }
1627:
1628: $options['props'] = $this->parseProps;
1629:
1630: $responsedescr = $this->PROPPATCH($options);
1631:
1632: $this->http_status("207 Multi-Status");
1633: header('Content-Type: text/xml; charset="utf-8"');
1634:
1635: echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
1636:
1637: echo "<D:multistatus xmlns:D=\"DAV:\">\n";
1638: echo " <D:response>\n";
1639: echo " <D:href>".$this->_urlencode($this->_mergePaths($this->_SERVER["SCRIPT_NAME"], $this->path))."</D:href>\n";
1640:
1641: foreach ($options["props"] as $prop) {
1642: echo " <D:propstat>\n";
1643: echo " <D:prop><$prop[name] xmlns=\"$prop[ns]\"/></D:prop>\n";
1644: echo " <D:status>HTTP/1.1 $prop[status]</D:status>\n";
1645: echo " </D:propstat>\n";
1646: }
1647:
1648: if ($responsedescr) {
1649: echo " <D:responsedescription>".
1650: htmlspecialchars($responsedescr).
1651: "</D:responsedescription>\n";
1652: }
1653:
1654: echo " </D:response>\n";
1655: echo "</D:multistatus>\n";
1656: } else {
1657: $this->http_status("423 Locked");
1658: }
1659: }
1660:
1661:
1662:
1663:
1664:
1665:
1666: 1667: 1668: 1669: 1670: 1671:
1672: function http_MKCOL()
1673: {
1674: $options = Array();
1675:
1676: $options["path"] = $this->path;
1677:
1678: $stat = $this->MKCOL($options);
1679:
1680: $this->http_status($stat);
1681: }
1682:
1683:
1684:
1685:
1686:
1687:
1688: 1689: 1690: 1691: 1692: 1693:
1694: function http_GET()
1695: {
1696:
1697: $options = Array();
1698: $options["path"] = $this->path;
1699:
1700: $this->_get_ranges($options);
1701:
1702: if (true === ($status = $this->GET($options))) {
1703: if (!headers_sent()) {
1704: $status = "200 OK";
1705:
1706: if (!isset($options['mimetype'])) {
1707: $options['mimetype'] = "application/octet-stream";
1708: }
1709: header("Content-type: $options[mimetype]");
1710:
1711: if (isset($options['mtime'])) {
1712: header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
1713: }
1714:
1715: if (isset($options['stream'])) {
1716:
1717: if (!empty($options['ranges']) && (0===fseek($options['stream'], 0, SEEK_SET))) {
1718:
1719:
1720: if (count($options['ranges']) === 1) {
1721: $range = $options['ranges'][0];
1722:
1723: if (isset($range['start'])) {
1724: fseek($options['stream'], $range['start'], SEEK_SET);
1725: if (feof($options['stream'])) {
1726: $this->http_status("416 Requested range not satisfiable");
1727: return;
1728: }
1729:
1730: if (isset($range['end'])) {
1731: $size = $range['end']-$range['start']+1;
1732: $this->http_status("206 partial");
1733: header("Content-length: $size");
1734: header("Content-range: $range[start]-$range[end]/"
1735: . (isset($options['size']) ? $options['size'] : "*"));
1736: while ($size && !feof($options['stream'])) {
1737: $buffer = fread($options['stream'], 4096);
1738: $size -= $this->bytes($buffer);
1739: echo $buffer;
1740: }
1741: } else {
1742: $this->http_status("206 partial");
1743: if (isset($options['size'])) {
1744: header("Content-length: ".($options['size'] - $range['start']));
1745: header("Content-range: ".$range['start']."-".$range['end']."/"
1746: . (isset($options['size']) ? $options['size'] : "*"));
1747: }
1748: fpassthru($options['stream']);
1749: }
1750: } else {
1751: header("Content-length: ".$range['last']);
1752: fseek($options['stream'], -$range['last'], SEEK_END);
1753: fpassthru($options['stream']);
1754: }
1755: } else {
1756: $this->_multipart_byterange_header();
1757: foreach ($options['ranges'] as $range) {
1758:
1759: if (isset($range['start'])) {
1760: $from = $range['start'];
1761: $to = !empty($range['end']) ? $range['end'] : $options['size']-1;
1762: } else {
1763: $from = $options['size'] - $range['last']-1;
1764: $to = $options['size'] -1;
1765: }
1766: $total = isset($options['size']) ? $options['size'] : "*";
1767: $size = $to - $from + 1;
1768: $this->_multipart_byterange_header($options['mimetype'], $from, $to, $total);
1769:
1770:
1771: fseek($options['stream'], $from, SEEK_SET);
1772: while ($size && !feof($options['stream'])) {
1773: $buffer = fread($options['stream'], 4096);
1774: $size -= $this->bytes($buffer);
1775: echo $buffer;
1776: }
1777: }
1778: $this->_multipart_byterange_header();
1779: }
1780: } else {
1781:
1782: if (isset($options['size'])) {
1783: header("Content-length: ".$options['size']);
1784: }
1785: fpassthru($options['stream']);
1786: return;
1787: }
1788: } elseif (isset($options['data'])) {
1789: if (is_array($options['data'])) {
1790:
1791: } else {
1792: header("Content-length: ".$this->bytes($options['data']));
1793: echo $options['data'];
1794: }
1795: }
1796: }
1797: }
1798:
1799: if (!headers_sent()) {
1800: if (false === $status) {
1801: $this->http_status("404 not found");
1802: } else {
1803:
1804: $this->http_status("$status");
1805: }
1806: }
1807: }
1808:
1809:
1810: 1811: 1812: 1813: 1814: 1815:
1816: function _get_ranges(&$options)
1817: {
1818:
1819: if (isset($this->_SERVER['HTTP_RANGE'])) {
1820:
1821:
1822: if (preg_match('/bytes\s*=\s*(.+)/', $this->_SERVER['HTTP_RANGE'], $matches)) {
1823: $options["ranges"] = array();
1824:
1825:
1826: foreach (explode(",", $matches[1]) as $range) {
1827:
1828: list($start, $end) = explode("-", $range);
1829: $options["ranges"][] = ($start==="")
1830: ? array("last"=>$end)
1831: : array("start"=>$start, "end"=>$end);
1832: }
1833: }
1834: }
1835: }
1836:
1837: 1838: 1839: 1840: 1841: 1842: 1843: 1844: 1845: 1846: 1847: 1848: 1849:
1850: function ($mimetype = false, $from = false, $to=false, $total=false)
1851: {
1852: if ($mimetype === false) {
1853: if (!isset($this->multipart_separator)) {
1854:
1855:
1856:
1857:
1858: $this->multipart_separator = "SEPARATOR_" . uniqid(mt_rand());
1859:
1860:
1861: header("Content-type: multipart/byteranges; boundary=".$this->multipart_separator);
1862: } else {
1863:
1864:
1865:
1866: echo "\n--{$this->multipart_separator}--";
1867: }
1868: } else {
1869:
1870: echo "\n--{$this->multipart_separator}\n";
1871: echo "Content-type: $mimetype\n";
1872: echo "Content-range: $from-$to/". ($total === false ? "*" : $total);
1873: echo "\n\n";
1874: }
1875: }
1876:
1877:
1878:
1879:
1880:
1881:
1882:
1883: 1884: 1885: 1886: 1887: 1888:
1889: function http_HEAD()
1890: {
1891: $status = false;
1892: $options = Array();
1893: $options["path"] = $this->path;
1894:
1895: if (method_exists($this, "HEAD")) {
1896: $status = $this->head($options);
1897: } else if (method_exists($this, "GET")) {
1898: ob_start();
1899: $status = $this->GET($options);
1900: if (!isset($options['size'])) {
1901: $options['size'] = ob_get_length();
1902: }
1903: ob_end_clean();
1904: }
1905:
1906: if (!isset($options['mimetype'])) {
1907: $options['mimetype'] = "application/octet-stream";
1908: }
1909: header("Content-type: $options[mimetype]");
1910:
1911: if (isset($options['mtime'])) {
1912: header("Last-modified:".gmdate("D, d M Y H:i:s ", $options['mtime'])."GMT");
1913: }
1914:
1915: if (isset($options['size'])) {
1916: header("Content-length: ".$options['size']);
1917: }
1918:
1919: if ($status === true) $status = "200 OK";
1920: if ($status === false) $status = "404 Not found";
1921:
1922: $this->http_status($status);
1923: }
1924:
1925:
1926:
1927:
1928:
1929: 1930: 1931: 1932: 1933: 1934:
1935: function http_PUT()
1936: {
1937: if ($this->_check_lock_status($this->path)) {
1938: $options = Array();
1939: $options["path"] = $this->path;
1940: $options["content_length"] = $this->_SERVER["CONTENT_LENGTH"];
1941:
1942:
1943: if (isset($this->_SERVER["CONTENT_TYPE"])) {
1944:
1945: if (!strncmp($this->_SERVER["CONTENT_TYPE"], "multipart/", 10)) {
1946: $this->http_status("501 not implemented");
1947: echo "The service does not support mulipart PUT requests";
1948: return;
1949: }
1950: $options["content_type"] = $this->_SERVER["CONTENT_TYPE"];
1951: } else {
1952:
1953: $options["content_type"] = "application/octet-stream";
1954: }
1955:
1956: 1957: 1958: 1959: 1960:
1961: foreach ($this->_SERVER as $key => $val) {
1962: if (strncmp($key, "HTTP_CONTENT", 11)) continue;
1963: switch ($key) {
1964: case 'HTTP_CONTENT_ENCODING':
1965:
1966: $this->http_status("501 not implemented");
1967: echo "The service does not support '$val' content encoding";
1968: return;
1969:
1970: case 'HTTP_CONTENT_LANGUAGE':
1971:
1972:
1973: $options["content_language"] = $val;
1974: break;
1975:
1976: case 'HTTP_CONTENT_LENGTH':
1977:
1978: break;
1979:
1980: case 'HTTP_CONTENT_LOCATION':
1981: 1982: 1983:
1984: break;
1985:
1986: case 'HTTP_CONTENT_RANGE':
1987:
1988:
1989:
1990: if (!preg_match('@bytes\s+(\d+)-(\d+)/((\d+)|\*)@', $val, $matches)) {
1991: $this->http_status("400 bad request");
1992: echo "The service does only support single byte ranges";
1993: return;
1994: }
1995:
1996: $range = array("start"=>$matches[1], "end"=>$matches[2]);
1997: if (is_numeric($matches[3])) {
1998: $range["total_length"] = $matches[3];
1999: }
2000: $option["ranges"][] = $range;
2001:
2002:
2003:
2004:
2005: break;
2006:
2007: case 'HTTP_CONTENT_TYPE':
2008:
2009: break;
2010:
2011: case 'HTTP_CONTENT_MD5':
2012:
2013: $this->http_status("501 not implemented");
2014: echo "The service does not support content MD5 checksum verification";
2015: return;
2016:
2017: default:
2018:
2019: $this->http_status("501 not implemented");
2020: echo "The service does not support '$key'";
2021: return;
2022: }
2023: }
2024:
2025: $options["stream"] = fopen("php://input", "r");
2026:
2027: $stat = $this->PUT($options);
2028:
2029: if ($stat === false) {
2030: $stat = "403 Forbidden";
2031: } else if (is_resource($stat) && get_resource_type($stat) == "stream") {
2032: $stream = $stat;
2033:
2034: $stat = $options["new"] ? "201 Created" : "204 No Content";
2035:
2036: if (!empty($options["ranges"])) {
2037:
2038: if (0 == fseek($stream, $range[0]["start"], SEEK_SET)) {
2039: $length = $range[0]["end"]-$range[0]["start"]+1;
2040: if (!fwrite($stream, fread($options["stream"], $length))) {
2041: $stat = "403 Forbidden";
2042: }
2043: } else {
2044: $stat = "403 Forbidden";
2045: }
2046: } else {
2047: while (!feof($options["stream"])) {
2048: if (false === fwrite($stream, fread($options["stream"], 4096))) {
2049: $stat = "403 Forbidden";
2050: break;
2051: }
2052: }
2053: }
2054:
2055: fclose($stream);
2056: }
2057:
2058: $this->http_status($stat);
2059: } else {
2060: $this->http_status("423 Locked");
2061: }
2062: }
2063:
2064:
2065:
2066:
2067:
2068:
2069: 2070: 2071: 2072: 2073: 2074:
2075: function http_DELETE()
2076: {
2077:
2078: if (isset($this->_SERVER["HTTP_DEPTH"])) {
2079: if ($this->_SERVER["HTTP_DEPTH"] != "infinity") {
2080: $this->http_status("400 Bad Request");
2081: return;
2082: }
2083: }
2084:
2085:
2086: if ($this->_check_lock_status($this->path)) {
2087:
2088: $options = Array();
2089: $options["path"] = $this->path;
2090:
2091: $stat = $this->DELETE($options);
2092:
2093: $this->http_status($stat);
2094: } else {
2095:
2096: $this->http_status("423 Locked");
2097: }
2098: }
2099:
2100:
2101:
2102:
2103:
2104: 2105: 2106: 2107: 2108: 2109:
2110: function http_COPY()
2111: {
2112:
2113:
2114: $this->_copymove("copy");
2115: }
2116:
2117:
2118:
2119:
2120:
2121: 2122: 2123: 2124: 2125: 2126:
2127: function http_MOVE()
2128: {
2129: if ($this->_check_lock_status($this->path)) {
2130:
2131: $this->_copymove("move");
2132: } else {
2133: $this->http_status("423 Locked");
2134: }
2135: }
2136:
2137:
2138:
2139:
2140:
2141:
2142: 2143: 2144: 2145: 2146: 2147:
2148: function http_LOCK()
2149: {
2150: $options = Array();
2151: $options["path"] = $this->path;
2152:
2153: if (isset($this->_SERVER['HTTP_DEPTH'])) {
2154: $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2155: } else {
2156: $options["depth"] = "infinity";
2157: }
2158:
2159: if (isset($this->_SERVER["HTTP_TIMEOUT"])) {
2160: $options["timeout"] = explode(",", $this->_SERVER["HTTP_TIMEOUT"]);
2161: }
2162:
2163: if (empty($this->_SERVER['CONTENT_LENGTH']) && !empty($this->_SERVER['HTTP_IF'])) {
2164:
2165: if (!$this->_check_lock_status($this->path)) {
2166: $this->http_status("423 Locked");
2167: return;
2168: }
2169:
2170:
2171: $options["locktoken"] = substr($this->_SERVER['HTTP_IF'], 2, -2);
2172: $options["update"] = $options["locktoken"];
2173:
2174:
2175: $options['owner'] = "unknown";
2176: $options['scope'] = "exclusive";
2177: $options['type'] = "write";
2178:
2179:
2180: $stat = $this->LOCK($options);
2181: } else {
2182:
2183: $lockinfo = $this->_parse_lockinfo("php://input");
2184: if (!$lockinfo->parseSuccess) {
2185: $this->http_status("400 bad request");
2186: }
2187:
2188:
2189: if (!$this->_check_lock_status($this->path, $this->lockscope === "shared")) {
2190: $this->http_status("423 Locked");
2191: return;
2192: }
2193:
2194:
2195: $options["scope"] = $this->lockscope;
2196: $options["type"] = $this->locktype;
2197: $options["owner"] = $this->owner;
2198: $options["locktoken"] = $this->_new_locktoken();
2199:
2200: $stat = $this->LOCK($options);
2201: }
2202:
2203: if (is_bool($stat)) {
2204: $http_stat = $stat ? "200 OK" : "423 Locked";
2205: } else {
2206: $http_stat = (string)$stat;
2207: }
2208: $this->http_status($http_stat);
2209:
2210: if ($http_stat{0} == 2) {
2211: if ($options["timeout"]) {
2212:
2213: if (is_array($options["timeout"])) {
2214: reset($options["timeout"]);
2215: $options["timeout"] = current($options["timeout"]);
2216: }
2217:
2218: if (is_numeric($options["timeout"])) {
2219:
2220:
2221: if ($options["timeout"]>1000000) {
2222: $timeout = "Second-".($options['timeout']-time());
2223: } else {
2224: $timeout = "Second-$options[timeout]";
2225: }
2226: } else {
2227:
2228:
2229:
2230: $timeout = $options["timeout"];
2231: }
2232: } else {
2233: $timeout = "Infinite";
2234: }
2235:
2236: header('Content-Type: text/xml; charset="utf-8"');
2237: header("Lock-Token: <$options[locktoken]>");
2238: echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
2239: echo "<D:prop xmlns:D=\"DAV:\">\n";
2240: echo " <D:lockdiscovery>\n";
2241: echo " <D:activelock>\n";
2242: echo " <D:lockscope><D:$options[scope]/></D:lockscope>\n";
2243: echo " <D:locktype><D:$options[type]/></D:locktype>\n";
2244: echo " <D:depth>$options[depth]</D:depth>\n";
2245: echo " <D:owner>$options[owner]</D:owner>\n";
2246: echo " <D:timeout>$timeout</D:timeout>\n";
2247: echo " <D:locktoken><D:href>$options[locktoken]</D:href></D:locktoken>\n";
2248: echo " </D:activelock>\n";
2249: echo " </D:lockdiscovery>\n";
2250: echo "</D:prop>\n\n";
2251: }
2252: }
2253:
2254:
2255:
2256:
2257:
2258:
2259: 2260: 2261: 2262: 2263: 2264:
2265: function http_UNLOCK()
2266: {
2267: $options = Array();
2268: $options["path"] = $this->path;
2269:
2270: if (isset($this->_SERVER['HTTP_DEPTH'])) {
2271: $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2272: } else {
2273: $options["depth"] = "infinity";
2274: }
2275:
2276:
2277: $options["token"] = substr(trim($this->_SERVER["HTTP_LOCK_TOKEN"]), 1, -1);
2278:
2279:
2280: $stat = $this->UNLOCK($options);
2281:
2282: $this->http_status($stat);
2283: }
2284:
2285:
2286:
2287:
2288:
2289:
2290:
2291: function _copymove($what)
2292: {
2293: $options = Array();
2294: $options["path"] = $this->path;
2295:
2296: if (isset($this->_SERVER["HTTP_DEPTH"])) {
2297: $options["depth"] = $this->_SERVER["HTTP_DEPTH"];
2298: } else {
2299: $options["depth"] = "infinity";
2300: }
2301:
2302: $http_header_host = preg_replace("/:80$/", "", $this->_SERVER["HTTP_HOST"]);
2303:
2304: $url = parse_url($this->_SERVER["HTTP_DESTINATION"]);
2305: $path = urldecode($url["path"]);
2306:
2307: if (isset($url["host"])) {
2308:
2309: $http_host = $url["host"];
2310: if (isset($url["port"]) && $url["port"] != 80)
2311: $http_host.= ":".$url["port"];
2312: } else {
2313:
2314: $http_host == $http_header_host;
2315: }
2316:
2317: if ($http_host == $http_header_host &&
2318: !strncmp($this->_SERVER["SCRIPT_NAME"], $path,
2319: strlen($this->_SERVER["SCRIPT_NAME"]))) {
2320: $options["dest"] = substr($path, strlen($this->_SERVER["SCRIPT_NAME"]));
2321: if (!$this->_check_lock_status($options["dest"])) {
2322: $this->http_status("423 Locked");
2323: return;
2324: }
2325:
2326: } else {
2327: $options["dest_url"] = $this->_SERVER["HTTP_DESTINATION"];
2328: }
2329:
2330:
2331: if (isset($this->_SERVER["HTTP_OVERWRITE"])) {
2332: $options["overwrite"] = $this->_SERVER["HTTP_OVERWRITE"] == "T";
2333: } else {
2334: $options["overwrite"] = true;
2335: }
2336:
2337: $stat = $this->$what($options);
2338: $this->http_status($stat);
2339: }
2340:
2341:
2342:
2343:
2344:
2345: 2346: 2347: 2348: 2349: 2350:
2351: function _allow()
2352: {
2353:
2354: $allow = array("OPTIONS" =>"OPTIONS");
2355:
2356:
2357:
2358:
2359: foreach (get_class_methods($this) as $method) {
2360: if (!strncmp("http_", $method, 5)) {
2361: $method = strtoupper(substr($method, 5));
2362: if (method_exists($this, $method)) {
2363: $allow[$method] = $method;
2364: }
2365: }
2366: }
2367:
2368:
2369: if (isset($allow["GET"]))
2370: $allow["HEAD"] = "HEAD";
2371:
2372:
2373: if (!method_exists($this, "checklock")) {
2374: unset($allow["LOCK"]);
2375: unset($allow["UNLOCK"]);
2376: }
2377:
2378: return $allow;
2379: }
2380:
2381:
2382:
2383: 2384: 2385: 2386: 2387: 2388: 2389: 2390:
2391: function mkprop()
2392: {
2393: $args = func_get_args();
2394: if (count($args) == 3) {
2395: return array("ns" => $args[0],
2396: "name" => $args[1],
2397: "val" => $args[2]);
2398: } else {
2399: return array("ns" => "DAV:",
2400: "name" => $args[0],
2401: "val" => $args[1]);
2402: }
2403: }
2404:
2405:
2406:
2407: 2408: 2409: 2410: 2411: 2412:
2413: function _check_auth()
2414: {
2415: $auth_type = isset($this->_SERVER["AUTH_TYPE"])
2416: ? $this->_SERVER["AUTH_TYPE"]
2417: : null;
2418:
2419: $auth_user = isset($this->_SERVER["PHP_AUTH_USER"])
2420: ? $this->_SERVER["PHP_AUTH_USER"]
2421: : null;
2422:
2423: $auth_pw = isset($this->_SERVER["PHP_AUTH_PW"])
2424: ? $this->_SERVER["PHP_AUTH_PW"]
2425: : null;
2426:
2427: if (method_exists($this, "checkAuth")) {
2428:
2429: return $this->checkAuth($auth_type, $auth_user, $auth_pw);
2430: } else if (method_exists($this, "check_auth")) {
2431:
2432: return $this->check_auth($auth_type, $auth_user, $auth_pw);
2433: } else {
2434:
2435: return true;
2436: }
2437: }
2438:
2439:
2440:
2441:
2442:
2443: 2444: 2445: 2446: 2447: 2448:
2449: function _new_locktoken()
2450: {
2451: return "opaquelocktoken:" . ((string)new Horde_Support_Uuid());
2452: }
2453:
2454:
2455:
2456:
2457:
2458: 2459: 2460: 2461: 2462: 2463: 2464:
2465: function ($string, &$pos)
2466: {
2467:
2468: while (ctype_space($string{$pos})) {
2469: ++$pos;
2470: }
2471:
2472:
2473: if (strlen($string) <= $pos) {
2474: return false;
2475: }
2476:
2477:
2478: $c = $string{$pos++};
2479:
2480:
2481: switch ($c) {
2482: case "<":
2483:
2484: $pos2 = strpos($string, ">", $pos);
2485: $uri = substr($string, $pos, $pos2 - $pos);
2486: $pos = $pos2 + 1;
2487: return array("URI", $uri);
2488:
2489: case "[":
2490:
2491: if ($string{$pos} == "W") {
2492: $type = "ETAG_WEAK";
2493: $pos += 2;
2494: } else {
2495: $type = "ETAG_STRONG";
2496: }
2497: $pos2 = strpos($string, "]", $pos);
2498: $etag = substr($string, $pos + 1, $pos2 - $pos - 2);
2499: $pos = $pos2 + 1;
2500: return array($type, $etag);
2501:
2502: case "N":
2503:
2504: $pos += 2;
2505: return array("NOT", "Not");
2506:
2507: default:
2508:
2509: return array("CHAR", $c);
2510: }
2511: }
2512:
2513: 2514: 2515: 2516: 2517: 2518:
2519: function ($str)
2520: {
2521: $pos = 0;
2522: $len = strlen($str);
2523: $uris = array();
2524:
2525:
2526: while ($pos < $len) {
2527:
2528: $token = $this->_if_header_lexer($str, $pos);
2529:
2530:
2531: if ($token[0] == "URI") {
2532: $uri = $token[1];
2533: $token = $this->_if_header_lexer($str, $pos);
2534: } else {
2535: $uri = "";
2536: }
2537:
2538:
2539: if ($token[0] != "CHAR" || $token[1] != "(") {
2540: return false;
2541: }
2542:
2543: $list = array();
2544: $level = 1;
2545: $not = "";
2546: while ($level) {
2547: $token = $this->_if_header_lexer($str, $pos);
2548: if ($token[0] == "NOT") {
2549: $not = "!";
2550: continue;
2551: }
2552: switch ($token[0]) {
2553: case "CHAR":
2554: switch ($token[1]) {
2555: case "(":
2556: $level++;
2557: break;
2558: case ")":
2559: $level--;
2560: break;
2561: default:
2562: return false;
2563: }
2564: break;
2565:
2566: case "URI":
2567: $list[] = $not."<$token[1]>";
2568: break;
2569:
2570: case "ETAG_WEAK":
2571: $list[] = $not."[W/'$token[1]']>";
2572: break;
2573:
2574: case "ETAG_STRONG":
2575: $list[] = $not."['$token[1]']>";
2576: break;
2577:
2578: default:
2579: return false;
2580: }
2581: $not = "";
2582: }
2583:
2584: if (isset($uris[$uri]) && is_array($uris[$uri])) {
2585: $uris[$uri] = array_merge($uris[$uri], $list);
2586: } else {
2587: $uris[$uri] = $list;
2588: }
2589: }
2590:
2591: return $uris;
2592: }
2593:
2594: 2595: 2596: 2597: 2598: 2599: 2600: 2601: 2602:
2603: function ()
2604: {
2605: if (isset($this->_SERVER["HTTP_IF"])) {
2606: $this->_if_header_uris =
2607: $this->_if_header_parser($this->_SERVER["HTTP_IF"]);
2608:
2609: foreach ($this->_if_header_uris as $uri => $conditions) {
2610: if ($uri == "") {
2611: $uri = $this->uri;
2612: }
2613:
2614: $state = true;
2615: foreach ($conditions as $condition) {
2616:
2617:
2618:
2619: if (!strncmp($condition, "<opaquelocktoken:", strlen("<opaquelocktoken"))) {
2620: if (!preg_match('/^<opaquelocktoken:[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}>$/', $condition)) {
2621: $this->http_status("423 Locked");
2622: return false;
2623: }
2624: }
2625: if (!$this->_check_uri_condition($uri, $condition)) {
2626: $this->http_status("412 Precondition failed");
2627: $state = false;
2628: break;
2629: }
2630: }
2631:
2632:
2633: if ($state == true) {
2634: return true;
2635: }
2636: }
2637: return false;
2638: }
2639: return true;
2640: }
2641:
2642: 2643: 2644: 2645: 2646: 2647: 2648: 2649: 2650: 2651:
2652: function _check_uri_condition($uri, $condition)
2653: {
2654:
2655:
2656:
2657:
2658:
2659: if (!strncmp("<DAV:", $condition, 5)) {
2660: return false;
2661: }
2662:
2663: return true;
2664: }
2665:
2666:
2667: 2668: 2669: 2670: 2671: 2672:
2673: function _check_lock_status($path, $exclusive_only = false)
2674: {
2675:
2676: if (method_exists($this, "checkLock")) {
2677:
2678: $lock = $this->checkLock($path);
2679:
2680:
2681: if (is_array($lock) && count($lock)) {
2682:
2683: if (!isset($this->_SERVER["HTTP_IF"]) || !strstr($this->_SERVER["HTTP_IF"], $lock["token"])) {
2684: if (!$exclusive_only || ($lock["scope"] !== "shared"))
2685: return false;
2686: }
2687: }
2688: }
2689: return true;
2690: }
2691:
2692:
2693:
2694:
2695:
2696: 2697: 2698: 2699: 2700: 2701:
2702: function lockdiscovery($path)
2703: {
2704:
2705: if (!method_exists($this, "checklock")) {
2706: return "";
2707: }
2708:
2709:
2710: $activelocks = "";
2711:
2712:
2713: $lock = $this->checklock($path);
2714:
2715:
2716: if (is_array($lock) && count($lock)) {
2717:
2718: if (!empty($lock["expires"])) {
2719: $timeout = "Second-".($lock["expires"] - time());
2720: } else if (!empty($lock["timeout"])) {
2721: $timeout = "Second-$lock[timeout]";
2722: } else {
2723: $timeout = "Infinite";
2724: }
2725:
2726:
2727: $activelocks.= "
2728: <D:activelock>
2729: <D:lockscope><D:$lock[scope]/></D:lockscope>
2730: <D:locktype><D:$lock[type]/></D:locktype>
2731: <D:depth>$lock[depth]</D:depth>
2732: <D:owner>$lock[owner]</D:owner>
2733: <D:timeout>$timeout</D:timeout>
2734: <D:locktoken><D:href>$lock[token]</D:href></D:locktoken>
2735: </D:activelock>
2736: ";
2737: }
2738:
2739:
2740: return $activelocks;
2741: }
2742:
2743: 2744: 2745: 2746: 2747: 2748:
2749: function http_status($status)
2750: {
2751:
2752: if ($status === true) {
2753: $status = "200 OK";
2754: }
2755:
2756:
2757: $this->_http_status = $status;
2758:
2759:
2760: header("HTTP/1.1 $status");
2761: header("X-WebDAV-Status: $status", true);
2762: }
2763:
2764: 2765: 2766: 2767: 2768: 2769: 2770: 2771: 2772:
2773: function _urlencode($url)
2774: {
2775: return strtr($url, array(" "=>"%20",
2776: "%"=>"%25",
2777: "&"=>"%26",
2778: "<"=>"%3C",
2779: ">"=>"%3E",
2780: ));
2781: }
2782:
2783: 2784: 2785: 2786: 2787: 2788: 2789: 2790:
2791: function _urldecode($path)
2792: {
2793: return rawurldecode($path);
2794: }
2795:
2796: 2797: 2798: 2799: 2800: 2801:
2802: function _slashify($path)
2803: {
2804: if ($path[strlen($path)-1] != '/') {
2805: $path = $path."/";
2806: }
2807: return $path;
2808: }
2809:
2810: 2811: 2812: 2813: 2814: 2815:
2816: function _unslashify($path)
2817: {
2818: if ($path[strlen($path)-1] == '/') {
2819: $path = substr($path, 0, strlen($path) -1);
2820: }
2821: return $path;
2822: }
2823:
2824: 2825: 2826: 2827: 2828: 2829: 2830:
2831: function _mergePaths($parent, $child)
2832: {
2833: if ($child{0} == '/') {
2834: return $this->_unslashify($parent).$child;
2835: } else {
2836: return $this->_slashify($parent).$child;
2837: }
2838: }
2839:
2840: function _prop2xml($prop)
2841: {
2842: $res = array();
2843:
2844:
2845: if ($prop["ns"]) {
2846: $key = $this->ns_hash[$prop['ns']] . ':' . $prop['name'];
2847:
2848: } else {
2849: $key = $prop['name'] . '#xmlns=""';
2850:
2851: }
2852:
2853:
2854: if (is_array($prop['val'] && isset($prop['val']['name']))) {
2855:
2856: $res[$key] = $this->_prop2xml($prop['val']);
2857: } elseif (is_array($prop['val'])) {
2858:
2859: foreach ($prop['val'] as $entry) {
2860: $res[$key] = $this->_prop2xml($entry);
2861: }
2862: } else {
2863:
2864: $res[$key] = $prop['val'];
2865: }
2866:
2867: return $res;
2868: }
2869:
2870: 2871: 2872: 2873: 2874: 2875:
2876: function bytes($str)
2877: {
2878: static $func_overload;
2879:
2880: if (is_null($func_overload))
2881: {
2882: $func_overload = @extension_loaded('mbstring') ? ini_get('mbstring.func_overload') : 0;
2883: }
2884: return $func_overload & 2 ? mb_strlen($str,'ascii') : strlen($str);
2885: }
2886:
2887:
2888: function _parse_propfind($path)
2889: {
2890:
2891: $this->parseSuccess = true;
2892:
2893:
2894: $this->parseProps = array();
2895:
2896:
2897: $this->parseDepth = 0;
2898:
2899:
2900: $had_input = false;
2901:
2902:
2903: $f_in = fopen($path, "r");
2904: if (!$f_in) {
2905: $this->parseSuccess = false;
2906: return;
2907: }
2908:
2909:
2910: $xml_parser = xml_parser_create_ns("UTF-8", " ");
2911:
2912:
2913: xml_set_element_handler($xml_parser,
2914: array(&$this, "_startPropinfoElement"),
2915: array(&$this, "_endPropinfoElement"));
2916:
2917:
2918: xml_parser_set_option($xml_parser,
2919: XML_OPTION_CASE_FOLDING, false);
2920:
2921:
2922:
2923: while ($this->parseSuccess && !feof($f_in)) {
2924: $line = fgets($f_in);
2925: if (is_string($line)) {
2926: $had_input = true;
2927: $this->parseSuccess &= xml_parse($xml_parser, $line, false);
2928: }
2929: }
2930:
2931:
2932: if ($had_input) {
2933: $this->parseSuccess &= xml_parse($xml_parser, "", true);
2934: }
2935:
2936:
2937: xml_parser_free($xml_parser);
2938:
2939:
2940: fclose($f_in);
2941:
2942:
2943: if(!count($this->parseProps)) $this->parseProps = "all";
2944: }
2945:
2946:
2947: 2948: 2949: 2950: 2951: 2952: 2953: 2954:
2955: function _startPropinfoElement($parser, $name, $attrs)
2956: {
2957:
2958: if (strstr($name, " ")) {
2959: list($ns, $tag) = explode(" ", $name);
2960: if ($ns == "")
2961: $this->parseSuccess = false;
2962: } else {
2963: $ns = "";
2964: $tag = $name;
2965: }
2966:
2967:
2968: if ($this->parseDepth == 1) {
2969: if ($tag == "allprop")
2970: $this->parseProps = "all";
2971:
2972: if ($tag == "propname")
2973: $this->parseProps = "names";
2974: }
2975:
2976:
2977: if ($this->parseDepth == 2) {
2978: $prop = array("name" => $tag);
2979: if ($ns)
2980: $prop["xmlns"] = $ns;
2981: $this->parseProps[] = $prop;
2982: }
2983:
2984:
2985: $this->parseDepth++;
2986: }
2987:
2988:
2989: 2990: 2991: 2992: 2993: 2994: 2995:
2996: function _endPropinfoElement($parser, $name)
2997: {
2998:
2999: $this->parseDepth--;
3000: }
3001:
3002: function _parse_lockinfo($path)
3003: {
3004:
3005: $this->parseSuccess = true;
3006:
3007:
3008: $had_input = false;
3009:
3010:
3011: $f_in = fopen($path, "r");
3012: if (!$f_in) {
3013: $this->parseSuccess = false;
3014: return;
3015: }
3016:
3017:
3018: $xml_parser = xml_parser_create_ns("UTF-8", " ");
3019:
3020:
3021: xml_set_element_handler($xml_parser,
3022: array(&$this, "_startLockElement"),
3023: array(&$this, "_endLockElement"));
3024: xml_set_character_data_handler($xml_parser,
3025: array(&$this, "_lockData"));
3026:
3027:
3028: xml_parser_set_option($xml_parser,
3029: XML_OPTION_CASE_FOLDING, false);
3030:
3031:
3032: while ($this->parseSuccess && !feof($f_in)) {
3033: $line = fgets($f_in);
3034: if (is_string($line)) {
3035: $had_input = true;
3036: $this->parseSuccess &= xml_parse($xml_parser, $line, false);
3037: }
3038: }
3039:
3040:
3041: if ($had_input) {
3042: $this->parseSuccess &= xml_parse($xml_parser, "", true);
3043: }
3044:
3045:
3046: $this->parseSuccess &= !empty($this->locktype);
3047: $this->parseSuccess &= !empty($this->lockscope);
3048:
3049:
3050: xml_parser_free($xml_parser);
3051:
3052:
3053: fclose($f_in);
3054: }
3055:
3056:
3057: 3058: 3059: 3060: 3061: 3062: 3063: 3064: 3065:
3066: function _startLockElement($parser, $name, $attrs)
3067: {
3068:
3069: if (strstr($name, " ")) {
3070: list($ns, $tag) = explode(" ", $name);
3071: } else {
3072: $ns = "";
3073: $tag = $name;
3074: }
3075:
3076:
3077: if ($this->collect_owner) {
3078:
3079: $ns_short = "";
3080: $ns_attr = "";
3081: if ($ns) {
3082: if ($ns == "DAV:") {
3083: $ns_short = "D:";
3084: } else {
3085: $ns_attr = " xmlns='$ns'";
3086: }
3087: }
3088: $this->owner .= "<$ns_short$tag$ns_attr>";
3089: } else if ($ns == "DAV:") {
3090:
3091: switch ($tag) {
3092: case "write":
3093: $this->locktype = $tag;
3094: break;
3095: case "exclusive":
3096: case "shared":
3097: $this->lockscope = $tag;
3098: break;
3099: case "owner":
3100: $this->collect_owner = true;
3101: break;
3102: }
3103: }
3104: }
3105:
3106: 3107: 3108: 3109: 3110: 3111: 3112: 3113:
3114: function _lockData($parser, $data)
3115: {
3116:
3117: if ($this->collect_owner) {
3118: $this->owner .= $data;
3119: }
3120: }
3121:
3122: 3123: 3124: 3125: 3126: 3127: 3128: 3129:
3130: function _endLockElement($parser, $name)
3131: {
3132:
3133: if (strstr($name, " ")) {
3134: list($ns, $tag) = explode(" ", $name);
3135: } else {
3136: $ns = "";
3137: $tag = $name;
3138: }
3139:
3140:
3141: if (($ns == "DAV:") && ($tag == "owner")) {
3142: $this->collect_owner = false;
3143: }
3144:
3145:
3146: if ($this->collect_owner) {
3147: $ns_short = "";
3148: $ns_attr = "";
3149: if ($ns) {
3150: if ($ns == "DAV:") {
3151: $ns_short = "D:";
3152: } else {
3153: $ns_attr = " xmlns='$ns'";
3154: }
3155: }
3156: $this->owner .= "</$ns_short$tag$ns_attr>";
3157: }
3158: }
3159:
3160: function _parse_proppatch($path)
3161: {
3162: $this->parseSuccess = true;
3163:
3164: $this->parseDepth = 0;
3165: $this->parseProps = array();
3166: $had_input = false;
3167:
3168: $f_in = fopen($path, "r");
3169: if (!$f_in) {
3170: $this->parseSuccess = false;
3171: return;
3172: }
3173:
3174: $xml_parser = xml_parser_create_ns("UTF-8", " ");
3175:
3176: xml_set_element_handler($xml_parser,
3177: array(&$this, "_startProppatchElement"),
3178: array(&$this, "_endProppatchElement"));
3179:
3180: xml_set_character_data_handler($xml_parser,
3181: array(&$this, "_proppatchData"));
3182:
3183: xml_parser_set_option($xml_parser,
3184: XML_OPTION_CASE_FOLDING, false);
3185:
3186: while($this->parseSuccess && !feof($f_in)) {
3187: $line = fgets($f_in);
3188: if (is_string($line)) {
3189: $had_input = true;
3190: $this->parseSuccess &= xml_parse($xml_parser, $line, false);
3191: }
3192: }
3193:
3194: if($had_input) {
3195: $this->parseSuccess &= xml_parse($xml_parser, "", true);
3196: }
3197:
3198: xml_parser_free($xml_parser);
3199:
3200: fclose($f_in);
3201: }
3202:
3203: 3204: 3205: 3206: 3207: 3208: 3209: 3210: 3211:
3212: function _startProppatchElement($parser, $name, $attrs)
3213: {
3214: if (strstr($name, " ")) {
3215: list($ns, $tag) = explode(" ", $name);
3216: if ($ns == "")
3217: $this->parseSuccess = false;
3218: } else {
3219: $ns = "";
3220: $tag = $name;
3221: }
3222:
3223: if ($this->parseDepth == 1) {
3224: $this->mode = $tag;
3225: }
3226:
3227: if ($this->parseDepth == 3) {
3228: $prop = array("name" => $tag);
3229: $this->current = array("name" => $tag, "ns" => $ns, "status"=> 200);
3230: if ($this->mode == "set") {
3231: $this->current["val"] = "";
3232: }
3233: }
3234:
3235: if ($this->parseDepth >= 4) {
3236: $this->current["val"] .= "<$tag";
3237: if (isset($attr)) {
3238: foreach ($attr as $key => $val) {
3239: $this->current["val"] .= ' '.$key.'="'.str_replace('"','"', $val).'"';
3240: }
3241: }
3242: $this->current["val"] .= ">";
3243: }
3244:
3245:
3246:
3247: $this->parseDepth++;
3248: }
3249:
3250: 3251: 3252: 3253: 3254: 3255: 3256: 3257:
3258: function _endProppatchElement($parser, $name)
3259: {
3260: if (strstr($name, " ")) {
3261: list($ns, $tag) = explode(" ", $name);
3262: if ($ns == "")
3263: $this->parseSuccess = false;
3264: } else {
3265: $ns = "";
3266: $tag = $name;
3267: }
3268:
3269: $this->parseDepth--;
3270:
3271: if ($this->parseDepth >= 4) {
3272: $this->current["val"] .= "</$tag>";
3273: }
3274:
3275: if ($this->parseDepth == 3) {
3276: if (isset($this->current)) {
3277: $this->parseProps[] = $this->current;
3278: unset($this->current);
3279: }
3280: }
3281: }
3282:
3283: 3284: 3285: 3286: 3287: 3288: 3289: 3290:
3291: function _proppatchData($parser, $data)
3292: {
3293: if (isset($this->current)) {
3294: $this->current["val"] .= $data;
3295: }
3296: }
3297:
3298: }
3299: