1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10: 11: 12: 13: 14:
15: class Horde_ActiveSync_Request_Sync extends Horde_ActiveSync_Request_Base
16: {
17:
18: const STATUS_SUCCESS = 1;
19: const STATUS_VERSIONMISM = 2;
20: const STATUS_KEYMISM = 3;
21: const STATUS_PROTERROR = 4;
22: const STATUS_SERVERERROR = 5;
23:
24:
25: const MAX_WINDOW_SIZE = 512;
26:
27: 28: 29: 30: 31: 32:
33: public function handle()
34: {
35: parent::handle();
36: $this->_logger->info('[' . $this->_device->id . '] Handling SYNC command.');
37:
38:
39: if (!$this->checkPolicyKey($this->_activeSync->getPolicyKey())) {
40: return false;
41: }
42:
43: $this->_statusCode = self::STATUS_SUCCESS;
44: $collections = array();
45:
46:
47: if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCHRONIZE)) {
48: throw new Horde_ActiveSync_Exception('Protocol error');
49: }
50: if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERS)) {
51: throw new Horde_ActiveSync_Exception('Protocol error');
52: }
53:
54: while ($this->_statusCode == self::STATUS_SUCCESS &&
55: $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDER)) {
56:
57:
58: $collection = array();
59: $collection['truncation'] = Horde_ActiveSync::TRUNCATION_ALL;
60: $collection['clientids'] = array();
61: $collection['fetchids'] = array();
62: $collection['windowsize'] = 100;
63: $collection['conflict'] = Horde_ActiveSync::CONFLICT_OVERWRITE_PIM;
64:
65: if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) {
66: throw new Horde_ActiveSync_Exception('Protocol error');
67: }
68:
69: $collection['class'] = $this->_decoder->getElementContent();
70: $this->_logger->info('[' . $this->_device->id . '] Syncing folder class: ' . $collection['class']);
71: if (!$this->_decoder->getElementEndTag()) {
72: throw new Horde_ActiveSync_Exception('Protocol error');
73: }
74:
75: if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCKEY)) {
76: throw new Horde_ActiveSync_Exception('Protocol error');
77: }
78: $collection['synckey'] = $this->_decoder->getElementContent();
79: if (!$this->_decoder->getElementEndTag()) {
80: throw new Horde_ActiveSync_Exception('Protocol error');
81: }
82:
83: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERID)) {
84: $collection['id'] = $this->_decoder->getElementContent();
85: $this->_logger->info('[' . $this->_device->id . '] Folder server id: ' . $collection['id']);
86: if (!$this->_decoder->getElementEndTag()) {
87: throw new Horde_ActiveSync_Exception('Protocol error');
88: }
89: }
90:
91:
92: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SUPPORTED)) {
93:
94: if ($collection['synckey'] != 0) {
95: $this->_statusCode = self::STATUS_PROTERROR;
96: $this->_handleError($collection);
97: exit;
98: }
99: while (1) {
100: $el = $this->_decoder->getElement();
101: if ($el[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
102: break;
103: }
104: $collection['supported'][] = $el[2];
105: }
106: }
107:
108: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DELETESASMOVES)) {
109: $collection['deletesasmoves'] = true;
110: }
111:
112: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_GETCHANGES)) {
113: $collection['getchanges'] = true;
114: }
115:
116: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WINDOWSIZE)) {
117: $collection['windowsize'] = $this->_decoder->getElementContent();
118: if (!$this->_decoder->getElementEndTag()) {
119: $this->_statusCode = self::STATUS_PROTERROR;
120: $this->_handleError($collection);
121: exit;
122: }
123:
124:
125: if ($collection['windowsize'] < 1 || $collection['windowsize'] > self::MAX_WINDOW_SIZE) {
126: $this->_logger->debug('[' . $this->_device->id . '] Bad windowsize sent, defaulting to 512');
127: $collection['windowsize'] = self::MAX_WINDOW_SIZE;
128: }
129: }
130:
131: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_OPTIONS)) {
132: while(1) {
133: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FILTERTYPE)) {
134: $collection['filtertype'] = $this->_decoder->getElementContent();
135: if (!$this->_decoder->getElementEndTag()) {
136: $this->_statusCode = self::STATUS_PROTERROR;
137: $this->_handleError($collection);
138: exit;
139: }
140: }
141: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_TRUNCATION)) {
142: $collection['truncation'] = $this->_decoder->getElementContent();
143: if (!$this->_decoder->getElementEndTag()) {
144: $this->_statusCode = self::STATUS_PROTERROR;
145: $this->_handleError($collection);
146: exit;
147: }
148: }
149: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_RTFTRUNCATION)) {
150: $collection['rtftruncation'] = $this->_decoder->getElementContent();
151: if (!$this->_decoder->getElementEndTag()) {
152: $this->_statusCode = self::STATUS_PROTERROR;
153: $this->_handleError($collection);
154: exit;
155: }
156: }
157:
158: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMESUPPORT)) {
159: $collection['mimesupport'] = $this->_decoder->getElementContent();
160: if (!$this->_decoder->getElementEndTag()) {
161: $this->_statusCode = self::STATUS_PROTERROR;
162: $this->_handleError($collection);
163: exit;
164: }
165: }
166:
167: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_MIMETRUNCATION)) {
168: $collection['mimetruncation'] = $this->_decoder->getElementContent();
169: if (!$this->_decoder->getElementEndTag()) {
170: $this->_statusCode = self::STATUS_PROTERROR;
171: $this->_handleError($collection);
172: exit;
173: }
174: }
175:
176: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CONFLICT)) {
177: $collection['conflict'] = $this->_decoder->getElementContent();
178: if (!$this->_decoder->getElementEndTag()) {
179: $this->_statusCode = self::STATUS_PROTERROR;
180: $this->_handleError;
181: exit;
182: }
183: }
184: $e = $this->_decoder->peek();
185: if ($e[Horde_ActiveSync_Wbxml::EN_TYPE] == Horde_ActiveSync_Wbxml::EN_TYPE_ENDTAG) {
186: $this->_decoder->getElementEndTag();
187: break;
188: }
189: }
190: }
191:
192: if ($this->_statusCode == self::STATUS_SUCCESS) {
193:
194: $this->_state->init($collection);
195: if (!empty($collection['supported'])) {
196:
197: if (empty($this->_device->supported)) {
198: $this->_device->supported = array();
199: }
200: $this->_device->supported[$collection['class']] = $collection['supported'];
201: $this->_state->setDeviceInfo($this->_device);
202: }
203:
204:
205: if (!isset($collection['id'])) {
206: $collection['id'] = $this->_state->getFolderData($this->_device->id, $collection['class']);
207: }
208:
209: try {
210: $this->_state->loadState($collection['synckey'], 'sync', $collection['id']);
211: } catch (Horde_ActiveSync_Exception $e) {
212: $this->_statusCode = self::STATUS_KEYMISM;
213: $this->_handleError($collection);
214: exit;
215: }
216: }
217:
218: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_COMMANDS)) {
219:
220:
221:
222: if (empty($collection['synckey'])) {
223: $this->_statusCode = self::STATUS_PROTERROR;
224: $this->_handleError($collection);
225: exit;
226: }
227:
228:
229: $importer = $this->_driver->getImporter();
230: $importer->init($this->_state, $collection['id'], $collection['conflict']);
231: $nchanges = 0;
232: while (1) {
233:
234: $element = $this->_decoder->getElement();
235: if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
236: $this->_decoder->_ungetElement($element);
237: break;
238: }
239:
240: $nchanges++;
241:
242: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SERVERENTRYID)) {
243: $serverid = $this->_decoder->getElementContent();
244:
245: if (!$this->_decoder->getElementEndTag()) {
246: $this->_statusCode = self::STATUS_PROTERROR;
247: $this->_handleError($collection);
248: exit;
249: }
250: } else {
251: $serverid = false;
252: }
253:
254: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CLIENTENTRYID)) {
255: $clientid = $this->_decoder->getElementContent();
256:
257: if (!$this->_decoder->getElementEndTag()) {
258: $this->_statusCode = self::STATUS_PROTERROR;
259: $this->_handleError($collection);
260: exit;
261: }
262: } else {
263: $clientid = false;
264: }
265:
266:
267: if ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA)) {
268: switch ($collection['class']) {
269: case 'Email':
270:
271:
272:
273:
274: $this->_statusCode = self::STATUS_SERVERERROR;
275: break;
276: case 'Contacts':
277: $appdata = new Horde_ActiveSync_Message_Contact(
278: array('logger' => $this->_logger,
279: 'protocolversion' => $this->_version));
280: $appdata->decodeStream($this->_decoder);
281: break;
282: case 'Calendar':
283: $appdata = new Horde_ActiveSync_Message_Appointment(array('logger' => $this->_logger));
284: $appdata->decodeStream($this->_decoder);
285: break;
286: case 'Tasks':
287: $appdata = new Horde_ActiveSync_Message_Task(array('logger' => $this->_logger));
288: $appdata->decodeStream($this->_decoder);
289: break;
290: }
291: if (!$this->_decoder->getElementEndTag()) {
292:
293: $this->_statusCode = self::STATUS_PROTERROR;
294: break;
295: }
296: }
297:
298: switch ($element[Horde_ActiveSync_Wbxml::EN_TAG]) {
299: case Horde_ActiveSync::SYNC_MODIFY:
300: if (isset($appdata)) {
301:
302:
303: if (isset($appdata->read)) {
304: $importer->importMessageReadFlag($serverid, $appdata->read);
305: } else {
306: $importer->importMessageChange($serverid, $appdata, $this->_device, false);
307: }
308: $collection['importedchanges'] = true;
309: }
310: break;
311: case Horde_ActiveSync::SYNC_ADD:
312: if (isset($appdata)) {
313: $id = $importer->importMessageChange(false, $appdata, $this->_device, $clientid);
314: if ($clientid && $id) {
315: $collection['clientids'][$clientid] = $id;
316: $collection['importedchanges'] = true;
317: }
318: }
319: break;
320: case Horde_ActiveSync::SYNC_REMOVE:
321: if (isset($collection['deletesasmoves'])) {
322: $folderid = $this->_driver->getWasteBasket();
323: if ($folderid) {
324: $importer->importMessageMove($serverid, $folderid);
325: $collection['importedchanges'] = true;
326: break;
327: }
328: }
329:
330: $importer->importMessageDeletion($serverid);
331: $collection['importedchanges'] = true;
332: break;
333: case Horde_ActiveSync::SYNC_FETCH:
334: array_push($collection['fetchids'], $serverid);
335: break;
336: }
337:
338: if (!$this->_decoder->getElementEndTag()) {
339:
340: $this->_statusCode = self::STATUS_PROTERROR;
341: $this->_handleError($collection);
342: exit;
343: }
344: }
345:
346: $this->_logger->debug(sprintf('[%s] Processed %d incoming changes', $this->_device->id, $nchanges));
347:
348: if (!$this->_decoder->getElementEndTag()) {
349:
350: $this->_statusCode = self::STATUS_PROTERROR;
351: $this->_handleError($collection);
352: exit;
353: }
354: }
355:
356: if (!$this->_decoder->getElementEndTag()) {
357:
358: $this->_statusCode = self::STATUS_PROTERROR;
359: $this->_handleError($collection);
360: exit;
361: }
362:
363: array_push($collections, $collection);
364: }
365:
366: if (!$this->_decoder->getElementEndTag()) {
367:
368: return false;
369: }
370:
371: if (!$this->_decoder->getElementEndTag()) {
372:
373: return false;
374: }
375:
376:
377: $this->_logger->info('[' . $this->_device->id . '] Beginning SYNC Response.');
378: $this->_encoder->startWBXML();
379: $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE);
380: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERS);
381: foreach ($collections as $collection) {
382: $changecount = 0;
383: if (isset($collection['getchanges'])) {
384: $filtertype = isset($collection['filtertype']) ? $collection['filtertype'] : false;
385: $exporter = new Horde_ActiveSync_Connector_Exporter($this->_encoder, $collection['class']);
386: $sync = $this->_driver->getSyncObject();
387: $sync->init($this->_state, $exporter, $collection);
388: $changecount = $sync->getChangeCount();
389: }
390:
391: $counter = Horde_ActiveSync_State_Base::getSyncKeyCounter($collection['synckey']);
392:
393: if (isset($collection['importedchanges']) ||
394: $changecount > 0 ||
395: $collection['synckey'] == '0' ||
396: $counter == '1') {
397: try {
398: $this->_logger->debug('Generating new synckey. Old synckey: ' . $collection['synckey']);
399: $collection['newsynckey'] = $this->_state->getNewSyncKey($collection['synckey']);
400: $this->_logger->debug('New synckey generated: ' . $collection['newsynckey']);
401: } catch (Horde_ActiveSync_Exception $e) {
402: $this->_statusCode = self::STATUS_KEYMISM;
403: }
404: }
405:
406: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER);
407: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE);
408: $this->_encoder->content($collection['class']);
409: $this->_encoder->endTag();
410:
411: $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCKEY);
412: if (isset($collection['newsynckey'])) {
413: $this->_encoder->content($collection['newsynckey']);
414: } else {
415: $this->_encoder->content($collection['synckey']);
416: }
417: $this->_encoder->endTag();
418:
419: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID);
420: $this->_encoder->content($collection['id']);
421: $this->_encoder->endTag();
422:
423: $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
424: $this->_encoder->content($this->_statusCode);
425: $this->_encoder->endTag();
426:
427:
428: $mimesupport = isset($collection['mimesupport']) ? $collection['mimesupport'] : 0;
429:
430:
431: if (isset($collection['clientids']) || count($collection['fetchids']) > 0) {
432: $this->_encoder->startTag(Horde_ActiveSync::SYNC_REPLIES);
433: foreach ($collection['clientids'] as $clientid => $serverid) {
434: $this->_encoder->startTag(Horde_ActiveSync::SYNC_ADD);
435: $this->_encoder->startTag(Horde_ActiveSync::SYNC_CLIENTENTRYID);
436: $this->_encoder->content($clientid);
437: $this->_encoder->endTag();
438: $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID);
439: $this->_encoder->content($serverid);
440: $this->_encoder->endTag();
441: $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
442: $this->_encoder->content(1);
443: $this->_encoder->endTag();
444: $this->_encoder->endTag();
445: }
446:
447:
448: foreach ($collection['fetchids'] as $id) {
449: $data = $this->_driver->fetch($collection['id'], $id, $mimesupport);
450: if ($data !== false) {
451: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FETCH);
452: $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID);
453: $this->_encoder->content($id);
454: $this->_encoder->endTag();
455: $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
456: $this->_encoder->content(1);
457: $this->_encoder->endTag();
458: $this->_encoder->startTag(Horde_ActiveSync::SYNC_DATA);
459: $data->encodeStream($this->_encoder);
460: $this->_encoder->endTag();
461: $this->_encoder->endTag();
462: } else {
463: $this->_logger->err(sprintf('[Horde_ActiveSync::handleSync] Unable to fetch %s', $id));
464: }
465: }
466: $this->_encoder->endTag();
467: }
468:
469:
470: if (isset($collection['getchanges'])) {
471:
472: if (!empty($collection['windowsize']) && $changecount > $collection['windowsize']) {
473: $this->_encoder->startTag(Horde_ActiveSync::SYNC_MOREAVAILABLE, false, true);
474: }
475:
476:
477: $this->_encoder->startTag(Horde_ActiveSync::SYNC_COMMANDS);
478:
479:
480: $n = 0;
481: while (1) {
482: $progress = $sync->syncronize();
483: if (!is_array($progress)) {
484: break;
485: }
486: $n++;
487:
488: if (!empty($collection['windowsize']) && $n >= $collection['windowsize']) {
489: $this->_logger->info(sprintf('[%s] Exported maxItems of messages: %d - more available.', $this->_device->id, $collection['windowsize']));
490: break;
491: }
492: }
493: $this->_encoder->endTag();
494: }
495:
496: $this->_encoder->endTag();
497:
498:
499: if (isset($collection['newsynckey'])) {
500: if (!empty($sync) || !empty($importer) || !empty($exporter) || $collection['synckey'] == 0) {
501: $this->_state->setNewSyncKey($collection['newsynckey']);
502: $this->_state->save();
503: } else {
504: $this->_logger->err(sprintf('[%s] Error saving %s - no state information available.', $this->_device->id, $collection['newsynckey']));
505: }
506: }
507: }
508:
509: $this->_encoder->endTag();
510:
511: $this->_encoder->endTag();
512:
513: return true;
514: }
515:
516: 517: 518: 519: 520:
521: private function _handleError($collection)
522: {
523: $this->_encoder->startWBXML();
524: $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE);
525:
526: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERS);
527:
528:
529: if ($this->_statusCode == self::STATUS_KEYMISM ||
530: isset($collection['importedchanges']) ||
531: isset($collection['getchanges']) ||
532: $collection['synckey'] == '0') {
533:
534: $collection['newsynckey'] = Horde_ActiveSync_State_Base::getNewSyncKey(($this->_statusCode == self::STATUS_KEYMISM) ? 0 : $collection['synckey']);
535: if ($collection['synckey'] != 0) {
536: $this->_state->init($collection);
537: $this->_state->removeState($collection['synckey']);
538: }
539: }
540:
541: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER);
542:
543: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE);
544: $this->_encoder->content($collection['class']);
545: $this->_encoder->endTag();
546:
547: $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCKEY);
548: if (isset($collection['newsynckey'])) {
549: $this->_encoder->content($collection['newsynckey']);
550: } else {
551: $this->_encoder->content($collection['synckey']);
552: }
553: $this->_encoder->endTag();
554:
555: $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID);
556: $this->_encoder->content($collection['id']);
557: $this->_encoder->endTag();
558:
559: $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
560: $this->_encoder->content($this->_statusCode);
561: $this->_encoder->endTag();
562:
563: $this->_encoder->endTag();
564: $this->_encoder->endTag();
565: $this->_encoder->endTag();
566: }
567:
568: }
569: