1: 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: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778: 779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792: 793: 794: 795: 796: 797: 798: 799: 800: 801: 802: 803: 804: 805: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824: 825: 826: 827: 828: 829: 830: 831: 832: 833: 834: 835: 836: 837: 838: 839: 840: 841: 842: 843: 844: 845: 846: 847: 848: 849: 850: 851: 852: 853: 854: 855: 856: 857: 858: 859: 860: 861: 862: 863: 864: 865: 866: 867: 868: 869: 870: 871: 872: 873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884: 885: 886: 887: 888: 889: 890: 891: 892: 893: 894: 895: 896: 897: 898: 899: 900: 901: 902: 903: 904: 905: 906: 907: 908: 909: 910: 911: 912: 913: 914: 915: 916: 917: 918: 919: 920: 921: 922: 923: 924: 925: 926: 927: 928: 929: 930: 931: 932: 933: 934: 935: 936: 937: 938: 939: 940: 941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952: 953: 954: 955: 956: 957: 958: 959: 960: 961: 962: 963: 964: 965: 966: 967: 968: 969: 970: 971: 972: 973: 974: 975: 976: 977: 978: 979: 980: 981: 982: 983: 984: 985: 986: 987: 988: 989: 990: 991: 992: 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: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282: 1283: 1284: 1285: 1286: 1287: 1288: 1289: 1290: 1291: 1292: 1293: 1294: 1295: 1296: 1297: 1298: 1299: 1300: 1301: 1302: 1303: 1304: 1305: 1306: 1307: 1308: 1309: 1310: 1311: 1312: 1313: 1314: 1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330: 1331: 1332: 1333: 1334: 1335: 1336: 1337: 1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346: 1347: 1348: 1349: 1350: 1351: 1352: 1353: 1354: 1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362: 1363: 1364: 1365: 1366: 1367: 1368: 1369: 1370: 1371: 1372: 1373: 1374: 1375: 1376: 1377: 1378: 1379: 1380: 1381: 1382: 1383: 1384: 1385: 1386: 1387: 1388: 1389: 1390: 1391: 1392: 1393: 1394: 1395: 1396: 1397: 1398: 1399: 1400: 1401: 1402: 1403: 1404: 1405: 1406: 1407: 1408: 1409: 1410: 1411: 1412: 1413: 1414: 1415: 1416: 1417: 1418: 1419: 1420: 1421: 1422: 1423: 1424: 1425: 1426:
<?php
require_once("AwlCache.php");
require_once("XMLDocument.php");
require_once("DAVPrincipal.php");
require_once("DAVTicket.php");
define('DEPTH_INFINITY', 9999);
class CalDAVRequest
{
var $options;
var $raw_post;
var $method;
var $depth;
var $principal;
var $current_user_principal_xml;
var $user_agent;
var $collection_id;
var $collection_path;
var $collection_type;
protected $exists;
var $destination;
protected $privileges;
var $supported_privileges;
public $ticket;
private $prefer;
function __construct( $options = array() ) {
global $session, $c, $debugging;
$this->options = $options;
if ( !isset($this->options['allow_by_email']) ) $this->options['allow_by_email'] = false;
if ( isset($_SERVER['HTTP_PREFER']) ) {
$this->prefer = explode( ',', $_SERVER['HTTP_PREFER']);
}
else if ( isset($_SERVER['HTTP_BRIEF']) && (strtoupper($_SERVER['HTTP_BRIEF']) == 'T') ) {
$this->prefer = array( 'return=minimal');
}
else
$this->prefer = array();
if ( isset($_SERVER['PATH_INFO']) ) {
$this->path = $_SERVER['PATH_INFO'];
}
else {
$this->path = '/';
if ( isset($_SERVER['REQUEST_URI']) ) {
if ( preg_match( '{^(.*?\.php)([^?]*)}', $_SERVER['REQUEST_URI'], $matches ) ) {
$this->path = $matches[2];
if ( substr($this->path,0,1) != '/' )
$this->path = '/'.$this->path;
}
else if ( $_SERVER['REQUEST_URI'] != '/' ) {
dbg_error_log('LOG', 'Server is not supplying PATH_INFO and REQUEST_URI does not include a PHP program. Wildly guessing "/"!!!');
}
}
}
$this->path = rawurldecode($this->path);
if ( preg_match( '#^(/[^/]+/[^/]+).ics$#', $this->path, $matches ) ) {
$this->path = $matches[1]. '/';
}
if ( isset($c->replace_path) && isset($c->replace_path['from']) && isset($c->replace_path['to']) ) {
$this->path = preg_replace($c->replace_path['from'], $c->replace_path['to'], $this->path);
}
$bad_chars_regex = '/[\\^\\[\\(\\\\]/';
if ( preg_match( $bad_chars_regex, $this->path ) ) {
$this->DoResponse( 400, translate("The calendar path contains illegal characters.") );
}
if ( strstr($this->path,'//') ) $this->path = preg_replace( '#//+#', '/', $this->path);
if ( !isset($c->raw_post) ) $c->raw_post = file_get_contents( 'php://input');
if ( isset($_SERVER['HTTP_CONTENT_ENCODING']) ) {
$encoding = $_SERVER['HTTP_CONTENT_ENCODING'];
@dbg_error_log('caldav', 'Content-Encoding: %s', $encoding );
$encoding = preg_replace('{[^a-z0-9-]}i','',$encoding);
if ( ! ini_get('open_basedir') && (isset($c->dbg['ALL']) || isset($c->dbg['caldav'])) ) {
$fh = fopen('/var/log/davical/encoded_data.debug'.$encoding,'w');
if ( $fh ) {
fwrite($fh,$c->raw_post);
fclose($fh);
}
}
switch( $encoding ) {
case 'gzip':
$this->raw_post = @gzdecode($c->raw_post);
break;
case 'deflate':
$this->raw_post = @gzinflate($c->raw_post);
break;
case 'compress':
$this->raw_post = @gzuncompress($c->raw_post);
break;
default:
}
if ( empty($this->raw_post) && !empty($c->raw_post) ) {
$this->PreconditionFailed(415, 'content-encoding', sprintf('Unable to decode "%s" content encoding.', $_SERVER['HTTP_CONTENT_ENCODING']));
}
$c->raw_post = $this->raw_post;
}
else {
$this->raw_post = $c->raw_post;
}
if ( isset($debugging) && isset($_GET['method']) ) {
$_SERVER['REQUEST_METHOD'] = $_GET['method'];
}
else if ( $_SERVER['REQUEST_METHOD'] == 'POST' && isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']) ){
$_SERVER['REQUEST_METHOD'] = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
}
$this->method = $_SERVER['REQUEST_METHOD'];
$this->content_type = (isset($_SERVER['CONTENT_TYPE']) ? $_SERVER['CONTENT_TYPE'] : null);
if ( preg_match( '{^(\S+/\S+)\s*(;.*)?$}', $this->content_type, $matches ) ) {
$this->content_type = $matches[1];
}
if ( strlen($c->raw_post) > 0 ) {
if ( $this->method == 'PROPFIND' || $this->method == 'REPORT' || $this->method == 'PROPPATCH' || $this->method == 'BIND' || $this->method == 'MKTICKET' || $this->method == 'ACL' ) {
if ( !preg_match( '{^(text|application)/xml$}', $this->content_type ) ) {
@dbg_error_log( "LOG request", 'Request is "%s" but client set content-type to "%s". Assuming they meant XML!',
$this->method, $this->content_type );
$this->content_type = 'text/xml';
}
}
else if ( $this->method == 'PUT' || $this->method == 'POST' ) {
$this->CoerceContentType();
}
}
else {
$this->content_type = 'text/plain';
}
$this->user_agent = ((isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : "Probably Mulberry"));
if ( isset($_SERVER['HTTP_DEPTH']) ) {
$this->depth = $_SERVER['HTTP_DEPTH'];
}
else {
switch( $this->method ) {
case 'DELETE':
case 'MOVE':
case 'COPY':
case 'LOCK':
$this->depth = 'infinity';
break;
case 'REPORT':
$this->depth = 0;
break;
case 'PROPFIND':
default:
$this->depth = 0;
}
}
if ( !is_int($this->depth) && "infinity" == $this->depth ) $this->depth = DEPTH_INFINITY;
$this->depth = intval($this->depth);
if ( isset($_SERVER['HTTP_DESTINATION']) ) {
$this->destination = $_SERVER['HTTP_DESTINATION'];
if ( preg_match('{^(https?)://([a-z.-]+)(:[0-9]+)?(/.*)$}', $this->destination, $matches ) ) {
$this->destination = $matches[4];
}
}
$this->overwrite = ( isset($_SERVER['HTTP_OVERWRITE']) && ($_SERVER['HTTP_OVERWRITE'] == 'F') ? false : true );
if ( isset($_SERVER['HTTP_IF']) ) $this->if_clause = $_SERVER['HTTP_IF'];
if ( isset($_SERVER['HTTP_LOCK_TOKEN']) && preg_match( '#[<]opaquelocktoken:(.*)[>]#', $_SERVER['HTTP_LOCK_TOKEN'], $matches ) ) {
$this->lock_token = $matches[1];
}
if ( isset($_GET['ticket']) ) {
$this->ticket = new DAVTicket($_GET['ticket']);
}
else if ( isset($_SERVER['HTTP_TICKET']) ) {
$this->ticket = new DAVTicket($_SERVER['HTTP_TICKET']);
}
if ( isset($_SERVER['HTTP_TIMEOUT']) ) {
$timeouts = explode( ',', $_SERVER['HTTP_TIMEOUT'] );
foreach( $timeouts AS $k => $v ) {
if ( strtolower($v) == 'infinite' ) {
$this->timeout = (isset($c->maximum_lock_timeout) ? $c->maximum_lock_timeout : 86400 * 100);
break;
}
elseif ( strtolower(substr($v,0,7)) == 'second-' ) {
$this->timeout = min( intval(substr($v,7)), (isset($c->maximum_lock_timeout) ? $c->maximum_lock_timeout : 86400 * 100) );
break;
}
}
if ( ! isset($this->timeout) || $this->timeout == 0 ) $this->timeout = (isset($c->default_lock_timeout) ? $c->default_lock_timeout : 900);
}
$this->principal = new Principal('path',$this->path);
$sql = "SELECT * FROM collection WHERE dav_name = :exact_name";
$params = array( ':exact_name' => $this->path );
if ( !preg_match( '#/$#', $this->path ) ) {
$sql .= " OR dav_name = :truncated_name OR dav_name = :trailing_slash_name";
$params[':truncated_name'] = preg_replace( '#[^/]*$#', '', $this->path);
$params[':trailing_slash_name'] = $this->path."/";
}
$sql .= " ORDER BY LENGTH(dav_name) DESC LIMIT 1";
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('caldav',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
if ( $row->dav_name == $this->path."/" ) {
$this->path = $row->dav_name;
dbg_error_log( "caldav", "Path is actually a collection - sending Content-Location header." );
header( "Content-Location: ".ConstructURL($this->path) );
}
$this->collection_id = $row->collection_id;
$this->collection_path = $row->dav_name;
$this->collection_type = ($row->is_calendar == 't' ? 'calendar' : 'collection');
$this->collection = $row;
if ( preg_match( '#^((/[^/]+/)\.(in|out)/)[^/]*$#', $this->path, $matches ) ) {
$this->collection_type = 'schedule-'. $matches[3]. 'box';
}
$this->collection->type = $this->collection_type;
}
else if ( preg_match( '{^( ( / ([^/]+) / ) \.(in|out)/ ) [^/]*$}x', $this->path, $matches ) ) {
$params = array( ':username' => $matches[3], ':parent_container' => $matches[2], ':dav_name' => $matches[1] );
$params[':boxname'] = ($matches[4] == 'in' ? ' Inbox' : ' Outbox');
$this->collection_type = 'schedule-'. $matches[4]. 'box';
$params[':resourcetypes'] = sprintf('<DAV::collection/><urn:ietf:params:xml:ns:caldav:%s/>', $this->collection_type );
$sql = <<<EOSQL
INSERT INTO collection ( user_no, parent_container, dav_name, dav_displayname, is_calendar, created, modified, dav_etag, resourcetypes )
VALUES( (SELECT user_no FROM usr WHERE username = text(:username)),
:parent_container, :dav_name,
(SELECT fullname FROM usr WHERE username = text(:username)) || :boxname,
FALSE, current_timestamp, current_timestamp, '1', :resourcetypes )
EOSQL;
$qry = new AwlQuery( $sql, $params );
$qry->Exec('caldav',__LINE__,__FILE__);
dbg_error_log( 'caldav', 'Created new collection as "%s".', trim($params[':boxname']) );
$cache = getCacheInstance();
$cache->delete( 'collection-'.$params[':dav_name'], null );
$cache->delete( 'principal-'.$params[':parent_container'], null );
$qry = new AwlQuery( "SELECT * FROM collection WHERE dav_name = :dav_name", array( ':dav_name' => $matches[1] ) );
if ( $qry->Exec('caldav',__LINE__,__FILE__) && $qry->rows() == 1 && ($row = $qry->Fetch()) ) {
$this->collection_id = $row->collection_id;
$this->collection_path = $matches[1];
$this->collection = $row;
$this->collection->type = $this->collection_type;
}
}
else if ( preg_match( '#^((/[^/]+/)calendar-proxy-(read|write))/?[^/]*$#', $this->path, $matches ) ) {
$this->collection_type = 'proxy';
$this->_is_proxy_request = true;
$this->proxy_type = $matches[3];
$this->collection_path = $matches[1].'/';
if ( $this->collection_path == $this->path."/" ) {
$this->path .= '/';
dbg_error_log( "caldav", "Path is actually a (proxy) collection - sending Content-Location header." );
header( "Content-Location: ".ConstructURL($this->path) );
}
}
else if ( $this->options['allow_by_email'] && preg_match( '#^/(\S+@\S+[.]\S+)/?$#', $this->path) ) {
$this->collection_id = -1;
$this->collection_type = 'email';
$this->collection_path = $this->path;
$this->_is_principal = true;
}
else if ( preg_match( '#^(/[^/?]+)/?$#', $this->path, $matches) || preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $this->path, $matches) ) {
$this->collection_id = -1;
$this->collection_path = $matches[1].'/';
$this->collection_type = 'principal';
$this->_is_principal = true;
if ( $this->collection_path == $this->path."/" ) {
$this->path .= '/';
dbg_error_log( "caldav", "Path is actually a collection - sending Content-Location header." );
header( "Content-Location: ".ConstructURL($this->path) );
}
if ( preg_match( '#^(/principals/[^/]+/[^/]+)/?$#', $this->path, $matches) ) {
$this->depth = 0;
}
}
else if ( $this->path == '/' ) {
$this->collection_id = -1;
$this->collection_path = '/';
$this->collection_type = 'root';
}
if ( $this->collection_path == $this->path ) $this->_is_collection = true;
dbg_error_log( "caldav", " Collection '%s' is %d, type %s", $this->collection_path, $this->collection_id, $this->collection_type );
$this->principal = new DAVPrincipal( array( "path" => $this->path, "options" => $this->options ) );
$this->user_no = $this->principal->user_no();
$this->username = $this->principal->username();
$this->by_email = $this->principal->byEmail();
$this->principal_id = $this->principal->principal_id();
if ( $this->collection_type == 'principal' || $this->collection_type == 'email' || $this->collection_type == 'proxy' ) {
$this->collection = $this->principal->AsCollection();
if( $this->collection_type == 'proxy' ) {
$this->collection->is_proxy = 't';
$this->collection->type = 'proxy';
$this->collection->proxy_type = $this->proxy_type;
$this->collection->dav_displayname = sprintf('Proxy %s for %s', $this->proxy_type, $this->principal->username() );
}
}
elseif( $this->collection_type == 'root' ) {
$this->collection = (object) array(
'collection_id' => 0,
'dav_name' => '/',
'dav_etag' => md5($c->system_name),
'is_calendar' => 'f',
'is_addressbook' => 'f',
'is_principal' => 'f',
'user_no' => 0,
'dav_displayname' => $c->system_name,
'type' => 'root',
'created' => date('Ymd\THis')
);
}
$this->setPermissions();
$this->supported_methods = array(
'OPTIONS' => '',
'PROPFIND' => '',
'REPORT' => '',
'DELETE' => '',
'LOCK' => '',
'UNLOCK' => '',
'MOVE' => '',
'ACL' => ''
);
if ( $this->IsCollection() ) {
switch ( $this->collection_type ) {
case 'root':
case 'email':
$this->supported_methods = array(
'OPTIONS' => '',
'PROPFIND' => '',
'REPORT' => ''
);
break;
case 'schedule-inbox':
case 'schedule-outbox':
$this->supported_methods = array_merge(
$this->supported_methods,
array(
'POST' => '', 'GET' => '', 'PUT' => '', 'HEAD' => '', 'PROPPATCH' => ''
)
);
break;
case 'calendar':
$this->supported_methods['GET'] = '';
$this->supported_methods['PUT'] = '';
$this->supported_methods['HEAD'] = '';
break;
case 'collection':
case 'principal':
$this->supported_methods['GET'] = '';
$this->supported_methods['PUT'] = '';
$this->supported_methods['HEAD'] = '';
$this->supported_methods['MKCOL'] = '';
$this->supported_methods['MKCALENDAR'] = '';
$this->supported_methods['PROPPATCH'] = '';
$this->supported_methods['BIND'] = '';
break;
}
}
else {
$this->supported_methods = array_merge(
$this->supported_methods,
array(
'GET' => '',
'HEAD' => '',
'PUT' => ''
)
);
}
$this->supported_reports = array(
'DAV::principal-property-search' => '',
'DAV::expand-property' => '',
'DAV::sync-collection' => ''
);
if ( isset($this->collection) && $this->collection->is_calendar ) {
$this->supported_reports = array_merge(
$this->supported_reports,
array(
'urn:ietf:params:xml:ns:caldav:calendar-query' => '',
'urn:ietf:params:xml:ns:caldav:calendar-multiget' => '',
'urn:ietf:params:xml:ns:caldav:free-busy-query' => ''
)
);
}
if ( isset($this->collection) && $this->collection->is_addressbook ) {
$this->supported_reports = array_merge(
$this->supported_reports,
array(
'urn:ietf:params:xml:ns:carddav:addressbook-query' => '',
'urn:ietf:params:xml:ns:carddav:addressbook-multiget' => ''
)
);
}
if ( isset($this->content_type) && preg_match( '#(application|text)/xml#', $this->content_type ) ) {
if ( !isset($this->raw_post) || $this->raw_post == '' ) {
$this->XMLResponse( 400, new XMLElement( 'error', new XMLElement('missing-xml'), array( 'xmlns' => 'DAV:') ) );
}
$xml_parser = xml_parser_create_ns('UTF-8');
$this->xml_tags = array();
xml_parser_set_option ( $xml_parser, XML_OPTION_SKIP_WHITE, 1 );
xml_parser_set_option ( $xml_parser, XML_OPTION_CASE_FOLDING, 0 );
$rc = xml_parse_into_struct( $xml_parser, $this->raw_post, $this->xml_tags );
if ( $rc == false ) {
dbg_error_log( 'ERROR', 'XML parsing error: %s at line %d, column %d',
xml_error_string(xml_get_error_code($xml_parser)),
xml_get_current_line_number($xml_parser), xml_get_current_column_number($xml_parser) );
$this->XMLResponse( 400, new XMLElement( 'error', new XMLElement('invalid-xml'), array( 'xmlns' => 'DAV:') ) );
}
xml_parser_free($xml_parser);
if ( count($this->xml_tags) ) {
dbg_error_log( "caldav", " Parsed incoming XML request body." );
}
else {
$this->xml_tags = null;
dbg_error_log( "ERROR", "Incoming request sent content-type XML with no XML request body." );
}
}
if ( isset($_SERVER["HTTP_IF_NONE_MATCH"]) ) {
$this->etag_none_match = $_SERVER["HTTP_IF_NONE_MATCH"];
if ( $this->etag_none_match == '' ) unset($this->etag_none_match);
}
if ( isset($_SERVER["HTTP_IF_MATCH"]) ) {
$this->etag_if_match = $_SERVER["HTTP_IF_MATCH"];
if ( $this->etag_if_match == '' ) unset($this->etag_if_match);
}
}
function setPermissions() {
global $c, $session;
if ( $this->path == '/' || $this->path == '' ) {
$this->privileges = privilege_to_bits( array('read','read-free-busy','read-acl'));
dbg_error_log( "caldav", "Full read permissions for user accessing /" );
}
else if ( $session->AllowedTo("Admin") || $session->principal->user_no() == $this->user_no ) {
$this->privileges = privilege_to_bits('all');
dbg_error_log( "caldav", "Full permissions for %s", ( $session->principal->user_no() == $this->user_no ? "user accessing their own hierarchy" : "a systems administrator") );
}
else {
$this->privileges = 0;
if ( $this->IsPublic() ) {
$this->privileges = privilege_to_bits(array('read','read-free-busy'));
dbg_error_log( "caldav", "Basic read permissions for user accessing a public collection" );
}
else if ( isset($c->public_freebusy_url) && $c->public_freebusy_url ) {
$this->privileges = privilege_to_bits('read-free-busy');
dbg_error_log( "caldav", "Basic free/busy permissions for user accessing a public free/busy URL" );
}
$params = array( ':session_principal_id' => $session->principal->principal_id(), ':scan_depth' => $c->permission_scan_depth );
if ( isset($this->by_email) && $this->by_email ) {
$sql ='SELECT pprivs( :session_principal_id::int8, :request_principal_id::int8, :scan_depth::int ) AS perm';
$params[':request_principal_id'] = $this->principal_id;
}
else {
$sql = 'SELECT path_privs( :session_principal_id::int8, :request_path::text, :scan_depth::int ) AS perm';
$params[':request_path'] = $this->path;
}
$qry = new AwlQuery( $sql, $params );
if ( $qry->Exec('caldav',__LINE__,__FILE__) && $permission_result = $qry->Fetch() )
$this->privileges |= bindec($permission_result->perm);
dbg_error_log( 'caldav', 'Restricted permissions for user accessing someone elses hierarchy: %s', decbin($this->privileges) );
if ( isset($this->ticket) && $this->ticket->MatchesPath($this->path) ) {
$this->privileges |= $this->ticket->privileges();
dbg_error_log( 'caldav', 'Applying permissions for ticket "%s" now: %s', $this->ticket->id(), decbin($this->privileges) );
}
}
$this->permissions = array();
$privs = bits_to_privilege($this->privileges);
foreach( $privs AS $k => $v ) {
switch( $v ) {
case 'DAV::all': $type = 'abstract'; break;
case 'DAV::write': $type = 'aggregate'; break;
default: $type = 'real';
}
$v = str_replace('DAV::', '', $v);
$this->permissions[$v] = $type;
}
}
function IsLocked() {
if ( !isset($this->_locks_found) ) {
$this->_locks_found = array();
$sql = 'DELETE FROM locks WHERE (start + timeout) < current_timestamp';
$qry = new AwlQuery($sql);
$qry->Exec('caldav',__LINE__,__FILE__);
$sql = 'SELECT * FROM locks WHERE :dav_name::text ~ (\'^\'||dav_name||:pattern_end_match)::text';
$qry = new AwlQuery($sql, array( ':dav_name' => $this->path, ':pattern_end_match' => ($this->IsInfiniteDepth() ? '' : '$') ) );
if ( $qry->Exec('caldav',__LINE__,__FILE__) ) {
while( $lock_row = $qry->Fetch() ) {
$this->_locks_found[$lock_row->opaquelocktoken] = $lock_row;
}
}
else {
$this->DoResponse(500,translate("Database Error"));
}
}
foreach( $this->_locks_found AS $lock_token => $lock_row ) {
if ( $lock_row->depth == DEPTH_INFINITY || $lock_row->dav_name == $this->path ) {
return $lock_token;
}
}
return false;
}
function IsPublic() {
if ( isset($this->collection) && isset($this->collection->publicly_readable) && $this->collection->publicly_readable == 't' ) {
return true;
}
return false;
}
private static function supportedPrivileges() {
return array(
'all' => array(
'read' => translate('Read the content of a resource or collection'),
'write' => array(
'bind' => translate('Create a resource or collection'),
'unbind' => translate('Delete a resource or collection'),
'write-content' => translate('Write content'),
'write-properties' => translate('Write properties')
),
'urn:ietf:params:xml:ns:caldav:read-free-busy' => translate('Read the free/busy information for a calendar collection'),
'read-acl' => translate('Read ACLs for a resource or collection'),
'read-current-user-privilege-set' => translate('Read the details of the current user\'s access control to this resource.'),
'write-acl' => translate('Write ACLs for a resource or collection'),
'unlock' => translate('Remove a lock'),
'urn:ietf:params:xml:ns:caldav:schedule-deliver' => array(
'urn:ietf:params:xml:ns:caldav:schedule-deliver-invite'=> translate('Deliver scheduling invitations from an organiser to this scheduling inbox'),
'urn:ietf:params:xml:ns:caldav:schedule-deliver-reply' => translate('Deliver scheduling replies from an attendee to this scheduling inbox'),
'urn:ietf:params:xml:ns:caldav:schedule-query-freebusy' => translate('Allow free/busy enquiries targeted at the owner of this scheduling inbox')
),
'urn:ietf:params:xml:ns:caldav:schedule-send' => array(
'urn:ietf:params:xml:ns:caldav:schedule-send-invite' => translate('Send scheduling invitations as an organiser from the owner of this scheduling outbox.'),
'urn:ietf:params:xml:ns:caldav:schedule-send-reply' => translate('Send scheduling replies as an attendee from the owner of this scheduling outbox.'),
'urn:ietf:params:xml:ns:caldav:schedule-send-freebusy' => translate('Send free/busy enquiries')
)
)
);
}
function dav_name() {
if ( isset($this->path) ) return $this->path;
return null;
}
function GetDepthName( ) {
if ( $this->IsInfiniteDepth() ) return 'infinity';
return $this->depth;
}
function DepthRegexTail( $for_collection_report = false) {
if ( $this->IsInfiniteDepth() ) return '';
if ( $this->depth == 0 && $for_collection_report ) return '[^/]+$';
if ( $this->depth == 0 ) return '$';
return '[^/]*/?$';
}
function GetLockRow( $lock_token ) {
if ( isset($this->_locks_found) && isset($this->_locks_found[$lock_token]) ) {
return $this->_locks_found[$lock_token];
}
$qry = new AwlQuery('SELECT * FROM locks WHERE opaquelocktoken = :lock_token', array( ':lock_token' => $lock_token ) );
if ( $qry->Exec('caldav',__LINE__,__FILE__) ) {
$lock_row = $qry->Fetch();
$this->_locks_found = array( $lock_token => $lock_row );
return $this->_locks_found[$lock_token];
}
else {
$this->DoResponse( 500, translate("Database Error") );
}
return false;
}
function ValidateLockToken( $lock_token ) {
if ( isset($this->lock_token) && $this->lock_token == $lock_token ) {
dbg_error_log( "caldav", "They supplied a valid lock token. Great!" );
return true;
}
if ( isset($this->if_clause) ) {
dbg_error_log( "caldav", "Checking lock token '%s' against '%s'", $lock_token, $this->if_clause );
$tokens = preg_split( '/[<>]/', $this->if_clause );
foreach( $tokens AS $k => $v ) {
dbg_error_log( "caldav", "Checking lock token '%s' against '%s'", $lock_token, $v );
if ( 'opaquelocktoken:' == substr( $v, 0, 16 ) ) {
if ( substr( $v, 16 ) == $lock_token ) {
dbg_error_log( "caldav", "Lock token '%s' validated OK against '%s'", $lock_token, $v );
return true;
}
}
}
}
else {
@dbg_error_log( "caldav", "Invalid lock token '%s' - not in Lock-token (%s) or If headers (%s) ", $lock_token, $this->lock_token, $this->if_clause );
}
return false;
}
function GetLockDetails( $lock_token ) {
if ( !isset($this->_locks_found) && false === $this->IsLocked() ) return false;
if ( isset($this->_locks_found[$lock_token]) ) return $this->_locks_found[$lock_token];
return false;
}
function FailIfLocked() {
if ( $existing_lock = $this->IsLocked() ) {
dbg_error_log( "caldav", "There is a lock on '%s'", $this->path);
if ( ! $this->ValidateLockToken($existing_lock) ) {
$lock_row = $this->GetLockRow($existing_lock);
$response[] = new XMLElement( 'response', array(
new XMLElement( 'href', $lock_row->dav_name ),
new XMLElement( 'status', 'HTTP/1.1 423 Resource Locked')
));
if ( $lock_row->dav_name != $this->path ) {
$response[] = new XMLElement( 'response', array(
new XMLElement( 'href', $this->path ),
new XMLElement( 'propstat', array(
new XMLElement( 'prop', new XMLElement( 'lockdiscovery' ) ),
new XMLElement( 'status', 'HTTP/1.1 424 Failed Dependency')
))
));
}
$response = new XMLElement( "multistatus", $response, array('xmlns'=>'DAV:') );
$xmldoc = $response->Render(0,'<?xml version="1.0" encoding="utf-8" ?>');
$this->DoResponse( 207, $xmldoc, 'text/xml; charset="utf-8"' );
}
return $existing_lock;
}
return false;
}
function CoerceContentType() {
if ( isset($this->content_type) ) {
$type = explode( '/', $this->content_type, 2);
if ( $type[0] == 'text' ) {
if ( !empty($type[1]) && ($type[1] == 'vcard' || $type[1] == 'calendar' || $type[1] == 'x-vcard') ) {
return;
}
}
}
$first_word = trim(substr( $this->raw_post, 0, 30));
$first_word = strtoupper( preg_replace( '/\s.*/s', '', $first_word ) );
switch( $first_word ) {
case '<?XML':
dbg_error_log( 'LOG WARNING', 'Application sent content-type of "%s" instead of "text/xml"',
(isset($this->content_type)?$this->content_type:'(null)') );
$this->content_type = 'text/xml';
break;
case 'BEGIN:VCALENDAR':
dbg_error_log( 'LOG WARNING', 'Application sent content-type of "%s" instead of "text/calendar"',
(isset($this->content_type)?$this->content_type:'(null)') );
$this->content_type = 'text/calendar';
break;
case 'BEGIN:VCARD':
dbg_error_log( 'LOG WARNING', 'Application sent content-type of "%s" instead of "text/vcard"',
(isset($this->content_type)?$this->content_type:'(null)') );
$this->content_type = 'text/vcard';
break;
default:
dbg_error_log( 'LOG NOTICE', 'Unusual content-type of "%s" and first word of content is "%s"',
(isset($this->content_type)?$this->content_type:'(null)'), $first_word );
}
if ( empty($this->content_type) ) $this->content_type = 'text/plain';
}
function PreferMinimal() {
if ( empty($this->prefer) ) return false;
foreach( $this->prefer AS $v ) {
if ( $v == 'return=minimal' ) return true;
if ( $v == 'return-minimal' ) return true;
}
return false;
}
function IsCollection( ) {
if ( !isset($this->_is_collection) ) {
$this->_is_collection = preg_match( '#/$#', $this->path );
}
return $this->_is_collection;
}
function IsCalendar( ) {
if ( !$this->IsCollection() || !isset($this->collection) ) return false;
return $this->collection->is_calendar == 't';
}
function IsAddressBook( ) {
if ( !$this->IsCollection() || !isset($this->collection) ) return false;
return $this->collection->is_addressbook == 't';
}
function IsPrincipal( ) {
if ( !isset($this->_is_principal) ) {
$this->_is_principal = preg_match( '#^/[^/]+/$#', $this->path );
}
return $this->_is_principal;
}
function IsProxyRequest( ) {
if ( !isset($this->_is_proxy_request) ) {
$this->_is_proxy_request = preg_match( '#^/[^/]+/calendar-proxy-(read|write)/?[^/]*$#', $this->path );
}
return $this->_is_proxy_request;
}
function IsInfiniteDepth( ) {
return ($this->depth == DEPTH_INFINITY);
}
function CollectionId( ) {
return $this->collection_id;
}
function BuildSupportedPrivileges( &$reply, $privs = null ) {
$privileges = array();
if ( $privs === null ) $privs = self::supportedPrivileges();
foreach( $privs AS $k => $v ) {
dbg_error_log( 'caldav', 'Adding privilege "%s" which is "%s".', $k, $v );
$privilege = new XMLElement('privilege');
$reply->NSElement($privilege,$k);
$privset = array($privilege);
if ( is_array($v) ) {
dbg_error_log( 'caldav', '"%s" is a container of sub-privileges.', $k );
$privset = array_merge($privset, $this->BuildSupportedPrivileges($reply,$v));
}
else if ( $v == 'abstract' ) {
dbg_error_log( 'caldav', '"%s" is an abstract privilege.', $v );
$privset[] = new XMLElement('abstract');
}
else if ( strlen($v) > 1 ) {
$privset[] = new XMLElement('description', $v);
}
$privileges[] = new XMLElement('supported-privilege',$privset);
}
return $privileges;
}
function AllowedTo( $activity ) {
global $session;
dbg_error_log('caldav', 'Checking whether "%s" is allowed to "%s"', $session->principal->username(), $activity);
if ( isset($this->permissions['all']) ) return true;
switch( $activity ) {
case 'all':
return false;
break;
case "CALDAV:schedule-send-freebusy":
return isset($this->permissions['read']) || isset($this->permissions['urn:ietf:params:xml:ns:caldav:read-free-busy']);
break;
case "CALDAV:schedule-send-invite":
return isset($this->permissions['read']) || isset($this->permissions['urn:ietf:params:xml:ns:caldav:read-free-busy']);
break;
case "CALDAV:schedule-send-reply":
return isset($this->permissions['read']) || isset($this->permissions['urn:ietf:params:xml:ns:caldav:read-free-busy']);
break;
case 'freebusy':
return isset($this->permissions['read']) || isset($this->permissions['urn:ietf:params:xml:ns:caldav:read-free-busy']);
break;
case 'delete':
return isset($this->permissions['write']) || isset($this->permissions['unbind']);
break;
case 'proppatch':
return isset($this->permissions['write']) || isset($this->permissions['write-properties']);
break;
case 'modify':
return isset($this->permissions['write']) || isset($this->permissions['write-content']);
break;
case 'create':
return isset($this->permissions['write']) || isset($this->permissions['bind']);
break;
case 'mkcalendar':
case 'mkcol':
if ( !isset($this->permissions['write']) || !isset($this->permissions['bind']) ) return false;
if ( $this->is_principal ) return false;
if ( $this->path == '/' ) return false;
break;
default:
$test_bits = privilege_to_bits( $activity );
return (($this->privileges & $test_bits) > 0 );
break;
}
return false;
}
function Privileges() {
return $this->privileges;
}
function CheckEtagMatch( $exists, $dest_etag ) {
global $c;
if ( ! $exists ) {
if ( (isset($this->etag_if_match) && $this->etag_if_match != '') ) {
$this->PreconditionFailed(412, 'if-match', translate('No resource exists at the destination.'));
}
}
else {
if ( isset($c->strict_etag_checking) && $c->strict_etag_checking )
$trim_chars = '\'\\" ';
else
$trim_chars = ' ';
if ( isset($this->etag_if_match) && $this->etag_if_match != '' && trim( $this->etag_if_match, $trim_chars) != trim( $dest_etag, $trim_chars ) ) {
$this->PreconditionFailed(412,'if-match',sprintf('Existing resource ETag of <<%s>> does not match <<%s>>', $dest_etag, $this->etag_if_match) );
}
else if ( isset($this->etag_none_match) && $this->etag_none_match != ''
&& ($this->etag_none_match == $dest_etag || $this->etag_none_match == '*') ) {
$this->PreconditionFailed(412,'if-none-match', translate( 'Existing resource matches "If-None-Match" header - not accepted.'));
}
}
}
function HavePrivilegeTo( $do_what ) {
$test_bits = privilege_to_bits( $do_what );
return ($this->privileges & $test_bits) > 0;
}
function UnsupportedRequest( $unsupported ) {
if ( isset($unsupported) && count($unsupported) > 0 ) {
$badprops = new XMLElement( "prop" );
foreach( $unsupported AS $k => $v ) {
dbg_error_log("ERROR", " %s: Support for $v:$k properties is not implemented yet", $this->method );
$badprops->NewElement(strtolower($k),false,array("xmlns" => strtolower($v)));
}
$error = new XMLElement("error", $badprops, array("xmlns" => "DAV:") );
$this->XMLResponse( 422, $error );
}
}
function NeedPrivilege( $privileges, $href=null ) {
if ( is_string($privileges) ) $privileges = array( $privileges );
if ( !isset($href) ) {
if ( $this->HavePrivilegeTo($privileges) ) return;
$href = $this->path;
}
$reply = new XMLDocument( array('DAV:' => '') );
$privnodes = array( $reply->href(ConstructURL($href)), new XMLElement( 'privilege' ) );
$reply->NSElement( $privnodes[1], $privileges[0] );
$xml = new XMLElement( 'need-privileges', new XMLElement( 'resource', $privnodes) );
$xmldoc = $reply->Render('error',$xml);
$this->DoResponse( 403, $xmldoc, 'text/xml; charset="utf-8"' );
exit(0);
}
function PreconditionFailed( $status, $precondition, $explanation = '', $xmlns='DAV:') {
$xmldoc = sprintf('<?xml version="1.0" encoding="utf-8" ?>
<error xmlns="%s">
<%s/>%s
</error>', $xmlns, str_replace($xmlns.':', '', $precondition), $explanation );
$this->DoResponse( $status, $xmldoc, 'text/xml; charset="utf-8"' );
exit(0);
}
function MalformedRequest( $text = 'Bad request' ) {
$this->DoResponse( 400, $text );
exit(0);
}
function XMLResponse( $status, $xmltree ) {
$xmldoc = $xmltree->Render(0,'<?xml version="1.0" encoding="utf-8" ?>');
$etag = md5($xmldoc);
if ( !headers_sent() ) header("ETag: \"$etag\"");
$this->DoResponse( $status, $xmldoc, 'text/xml; charset="utf-8"' );
exit(0);
}
public static function kill_on_exit() {
posix_kill( getmypid(), 28 );
}
function DoResponse( $status, $message="", $content_type="text/plain; charset=\"utf-8\"" ) {
global $session, $c;
if ( !headers_sent() ) @header( sprintf("HTTP/1.1 %d %s", $status, getStatusMessage($status)) );
if ( !headers_sent() ) @header( sprintf("X-DAViCal-Version: DAViCal/%d.%d.%d; DB/%d.%d.%d", $c->code_major, $c->code_minor, $c->code_patch, $c->schema_major, $c->schema_minor, $c->schema_patch) );
if ( !headers_sent() ) header( "Content-type: ".$content_type );
if ( (isset($c->dbg['ALL']) && $c->dbg['ALL']) || (isset($c->dbg['response']) && $c->dbg['response'])
|| $status == 400 || $status == 402 || $status == 403 || $status > 404 ) {
@dbg_error_log( "LOG ", 'Response status %03d for %s %s', $status, $this->method, $_SERVER['REQUEST_URI'] );
$lines = headers_list();
dbg_error_log( "LOG ", "***************** Response Header ****************" );
foreach( $lines AS $v ) {
dbg_error_log( "LOG headers", "-->%s", $v );
}
dbg_error_log( "LOG ", "******************** Response ********************" );
$lines = preg_split( '#[\r\n]+#', $message);
foreach( $lines AS $v ) {
dbg_error_log( "LOG response", "-->%s", $v );
}
}
$script_finish = microtime(true);
$script_time = $script_finish - $c->script_start_time;
$message_length = strlen($message);
if ( $message != '' ) {
if ( !headers_sent() ) header( "Content-Length: ".$message_length );
echo $message;
}
if ( isset($c->dbg['caldav']) && $c->dbg['caldav'] ) {
if ( $message_length > 100 || strstr($message, "\n") ) {
$message = substr( preg_replace("#\s+#m", ' ', $message ), 0, 100) . ($message_length > 100 ? "..." : "");
}
dbg_error_log("caldav", "Status: %d, Message: %s, User: %d, Path: %s", $status, $message, $session->principal->user_no(), $this->path);
}
if ( isset($c->dbg['statistics']) && $c->dbg['statistics'] ) {
$memory = '';
if ( function_exists('memory_get_usage') ) {
$memory = sprintf( ', Memory: %dk, Peak: %dk', memory_get_usage()/1024, memory_get_peak_usage(true)/1024);
}
@dbg_error_log("statistics", "Method: %s, Status: %d, Script: %5.3lfs, Queries: %5.3lfs, URL: %s%s",
$this->method, $status, $script_time, $c->total_query_time, $this->path, $memory);
}
try {
@ob_flush();
}
catch( Exception $ignored ) {}
if ( isset($c->metrics_style) && $c->metrics_style !== false ) {
$flush_time = microtime(true) - $script_finish;
$this->DoMetrics($status, $message_length, $script_time, $flush_time);
}
if ( isset($c->exit_after_memory_exceeds) && function_exists('memory_get_peak_usage') && memory_get_peak_usage(true) > $c->exit_after_memory_exceeds ) {
@dbg_error_log("statistics", "Peak memory use exceeds %d bytes (%d) - killing process %d", $c->exit_after_memory_exceeds, memory_get_peak_usage(true), getmypid());
register_shutdown_function( 'CalDAVRequest::kill_on_exit' );
}
exit(0);
}
function DoMetrics($status, $response_size, $script_time, $flush_time) {
global $c;
static $ns = 'metrics';
$method = (empty($this->method) ? 'UNKNOWN' : $this->method);
if ( $c->metrics_style != 'counters' ) {
$cache = getCacheInstance();
if ( $cache->isActive() ) {
$base_key = $method.':';
$count_like_this = $cache->increment( $ns, $base_key.$status );
$cache->increment( $ns, $base_key.'size', $response_size );
$cache->increment( $ns, $base_key.'script_time', intval($script_time * 1000000) );
$cache->increment( $ns, $base_key.'flush_time', intval($flush_time * 1000000) );
$cache->increment( $ns, $base_key.'query_time', intval($c->total_query_time * 1000000) );
if ( $count_like_this == 1 ) {
try {
$index = unserialize($cache->get($ns, 'index'));
} catch (Exception $e) {
$index = array('methods' => array(), 'statuses' => array());
}
$index['methods'][$method] = 1;
$index['statuses'][$status] = 1;
$cache->set($ns, 'index', serialize($index), 0);
}
}
else {
error_log("Full statistics are only available with a working Memcache configuration");
}
}
if ( $c->metrics_style != 'memcache' ) {
$qstring = "SELECT nextval('%s')";
switch( $method ) {
case 'OPTIONS':
case 'REPORT':
case 'PROPFIND':
case 'GET':
case 'PUT':
case 'HEAD':
case 'PROPPATCH':
case 'POST':
case 'MKCALENDAR':
case 'MKCOL':
case 'DELETE':
case 'MOVE':
case 'ACL':
case 'LOCK':
case 'UNLOCK':
case 'MKTICKET':
case 'DELTICKET':
case 'BIND':
$counter = strtolower($this->method);
break;
default:
$counter = 'unknown';
break;
}
$qry = new AwlQuery( "SELECT nextval('metrics_count_" . $counter . "')" );
$qry->Exec('always',__LINE__,__FILE__);
}
}
}