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: class Horde_SyncMl_ContentHandler
 28: {
 29:      30:  31:  32:  33:  34: 
 35:     protected $_Stack = array();
 36: 
 37:      38:  39: 
 40:     protected $_chars;
 41: 
 42:      43:  44:  45:  46:  47: 
 48:     protected $_currentCommand;
 49: 
 50:      51:  52: 
 53:     protected $_gotFinal = false;
 54: 
 55:     protected $_xmlWriter;
 56: 
 57:     protected $_wbxmlparser = null;
 58: 
 59:      60:  61:  62:  63:  64:  65: 
 66:     protected $_respURI;
 67: 
 68:     public $debug = false;
 69: 
 70:     public function __construct()
 71:     {
 72:          73:  74: 
 75:         $GLOBALS['message_expectresponse'] = false;
 76:     }
 77: 
 78:      79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89: 
 90:     public function process($request, $contentType, $respURI = null)
 91:     {
 92:         $isWBXML = $contentType =='application/vnd.syncml+wbxml';
 93:         $this->_respURI = $respURI;
 94: 
 95:          96:  97: 
 98:         ob_start();
 99: 
100:         $GLOBALS['backend']->logFile(Horde_SyncMl_Backend::LOGFILE_CLIENTMESSAGE, $request, $isWBXML);
101: 
102:         if (!$isWBXML) {
103:             
104: 
105:             
106:             if (preg_match('/^\s*<\?xml[^>]*encoding\s*=\s*"([^"]*)"/i',
107:                            $request, $m)) {
108:                 $charset = $m[1];
109:             } else {
110:                 $charset = 'UTF-8';
111:             }
112: 
113:             $GLOBALS['backend']->setCharset($charset);
114: 
115:             
116:             $this->_xmlWriter = &Horde_SyncMl_XmlOutput::singleton();
117:             118: 
119:             $this->_xmlWriter->init(new Horde_Xml_Wbxml_ContentHandler());
120: 
121:             
122:             $parser = xml_parser_create_ns($charset);
123:             xml_set_object($parser, $this);
124:             xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
125:             xml_set_element_handler($parser, '_startElement', '_endElement');
126:             xml_set_character_data_handler($parser, '_characters');
127:             xml_set_processing_instruction_handler($parser, '');
128:             xml_set_external_entity_ref_handler($parser, '');
129: 
130:             
131:             if (!xml_parse($parser, $request)) {
132:                 $s = sprintf('XML error: %s at line %d',
133:                              xml_error_string(xml_get_error_code($parser)),
134:                              xml_get_current_line_number($parser));
135:                 $GLOBALS['backend']->logMessage($s, 'ERR');
136:                 xml_parser_free($parser);
137:                 return new PEAR_Error($s);
138:             }
139: 
140:             xml_parser_free($parser);
141: 
142:         } else {
143:             144: 
145:             $this->_wbxmlparser = new Horde_Xml_Wbxml_Decoder();
146:             $this->_wbxmlparser->setContentHandler($this);
147: 
148:             
149:             $this->_xmlWriter = &Horde_SyncMl_XmlOutput::singleton();
150:             $this->_xmlWriter->init(new Horde_Xml_Wbxml_Encoder());
151: 
152:             
153:             
154:             $this->_wbxmlparser->decode($request);
155:         }
156: 
157:         $id = @session_id();
158:         $sessionclose = empty($id);
159: 
160:         $output = $this->getOutput();
161:         if (!$isWBXML) {
162:             $output = '<?xml version="1.0" encoding="' . $charset . '"?>' . $output;
163:         }
164:         $GLOBALS['backend']->logFile(Horde_SyncMl_Backend::LOGFILE_SERVERMESSAGE, $output, $isWBXML, $sessionclose);
165: 
166:         167: 
168:         $errorLogging = ob_get_clean();
169: 
170:         if (!empty($errorLogging)) {
171:             $GLOBALS['backend']->logMessage('Caught output: ' . $errorLogging, 'WARN');
172:         }
173: 
174:         return $output;
175:     }
176: 
177:     178: 179: 180: 181: 182: 
183: 
184:     185: 186: 187: 188: 
189:     public function getOutput()
190:     {
191:         return $this->_xmlWriter->getOutput();
192:     }
193: 
194:     195: 196: 
197:     protected function _startElement($parser, $tag, $attributes)
198:     {
199:         list($uri, $name) = $this->_splitURI($tag);
200:         $this->startElement($uri, $name, $attributes);
201:     }
202: 
203:     204: 205: 
206:     protected function _characters($parser, $chars)
207:     {
208:         $this->characters($chars);
209:     }
210: 
211:     212: 213: 
214:     protected function _endElement($parser, $tag)
215:     {
216:         list($uri, $name) = $this->_splitURI($tag);
217:         $this->endElement($uri, $name);
218:     }
219: 
220:     221: 222: 
223:     protected function _splitURI($tag)
224:     {
225:         $parts = explode(':', $tag);
226:         $name = array_pop($parts);
227:         $uri = implode(':', $parts);
228:         return array($uri, $name);
229:     }
230: 
231:     232: 233: 
234:     public function startElement($uri, $element, $attrs)
235:     {
236:         $this->_Stack[] = $element;
237: 
238:         
239:         if (count($this->_Stack) == 1) {
240:             return;
241:         }
242: 
243:         
244:         if ($this->_Stack[1] == 'SyncHdr') {
245:             if (count($this->_Stack) == 2) {
246:                 $this->_currentCommand = new Horde_SyncMl_Command_SyncHdr($this->_xmlWriter);
247:             }
248:             $this->_currentCommand->startElement($uri, $element, $attrs);
249:         } else {
250:             switch (count($this->_Stack)) {
251:             case 2:
252:                  
253:                  break;
254:             case 3:
255:                 
256:                 
257:                 $this->_currentCommand = &Horde_SyncMl_Command::factory($element,$this->_xmlWriter);
258:                 $this->_currentCommand->startElement($uri, $element, $attrs);
259:                 break;
260:             default:
261:                 
262:                 
263:                 $this->_currentCommand->startElement($uri, $element, $attrs);
264:                 break;
265:             }
266:         }
267:     }
268: 
269:     270: 271: 
272:     public function endElement($uri, $element)
273:     {
274:         
275:         if (count($this->_Stack) == 1) {
276:             return;
277:         }
278:         
279:         if ($this->_Stack[1] == 'SyncHdr') {
280:             switch (count($this->_Stack)) {
281:             case 2:
282:                 
283:                 $this->handleHeader($this->_currentCommand);
284:                 if ($this->debug) {
285:                     var_dump($this->_currentCommand);
286:                 }
287:                 unset($this->_currentCommand);
288:                 break;
289:             default:
290:                 
291:                 $this->_currentCommand->endElement($uri, $element);
292:                 break;
293:             }
294:         } else {
295:             switch (count($this->_Stack)) {
296:             case 2:
297:                 
298:                 $this->handleEnd();
299:                 break;
300:             case 3:
301:                 
302:                 
303:                 $this->_currentCommand->endElement($uri, $element);
304:                 $this->handleCommand($this->_currentCommand);
305:                 if ($this->debug) {
306:                     var_dump($this->_currentCommand);
307:                 }
308:                 unset($this->_currentCommand);
309:                 break;
310:             default:
311:                 
312:                 
313:                 $this->_currentCommand->endElement($uri, $element);
314:                 break;
315:             }
316:         }
317: 
318:         if (isset($this->_chars)) {
319:             unset($this->_chars);
320:         }
321: 
322:         array_pop($this->_Stack);
323:     }
324: 
325:     326: 327: 
328:     public function characters($str)
329:     {
330:         if (isset($this->_currentCommand)) {
331:             $this->_currentCommand->characters($str);
332:         } else {
333:             if (isset($this->_chars)) {
334:                 $this->_chars = $this->_chars . $str;
335:             } else {
336:                 $this->_chars = $str;
337:             }
338:         }
339:     }
340: 
341:     342: 343: 344: 345: 346: 
347: 
348:     349: 350: 351: 352: 
353:     public function handleHeader(&$hdr)
354:     {
355:         if (is_object($this->_wbxmlparser)) {
356:             357: 
358:             $this->_xmlWriter->output->setVersion($this->_wbxmlparser->getVersion());
359:             $this->_xmlWriter->output->setCharset($this->_wbxmlparser->getCharsetStr());
360:             $GLOBALS['backend']->setCharset($this->_wbxmlparser->getCharsetStr());
361:         }
362: 
363:         
364:         $hdr->setupState();
365:         $state = $GLOBALS['backend']->state;
366:         $state->wbxml = $this->_xmlWriter->isWBXML();
367: 
368:         
369:         if (!$state->authenticated) {
370:             $auth = $GLOBALS['backend']->checkAuthentication(
371:                 $hdr->user, $hdr->credData, $hdr->credFormat, $hdr->credType);
372:             if ($auth !== false) {
373:                 $state->authenticated = true;
374:                 $statuscode = Horde_SyncMl::RESPONSE_AUTHENTICATION_ACCEPTED;
375:                 $state->user = $auth;
376:                 $GLOBALS['backend']->setUser($auth);
377:             } else {
378:                 if (!$hdr->credData) {
379:                     $statuscode = Horde_SyncMl::RESPONSE_CREDENTIALS_MISSING;
380:                 } else {
381:                     $statuscode = Horde_SyncMl::RESPONSE_INVALID_CREDENTIALS;
382:                 }
383:                 $GLOBALS['backend']->logMessage('Invalid authentication', 'DEBUG');
384:             }
385:         } else {
386:             $statuscode = Horde_SyncMl::RESPONSE_OK;
387:             $GLOBALS['backend']->setUser($state->user);
388:         }
389: 
390:         
391:         $this->_xmlWriter->outputInit();
392: 
393:         
394:         $this->_xmlWriter->outputHeader($this->_respURI);
395: 
396:         
397:         $this->_xmlWriter->outputBodyStart();
398: 
399:         
400:         $this->_xmlWriter->outputStatus('0', 'SyncHdr', $statuscode,
401:                                         $state->targetURI,
402:                                         $state->sourceURI);
403: 
404:         
405:         $str = 'Authenticated: ' . ($state->authenticated ? 'yes' : 'no')
406:             . '; version: ' . $state->getVerDTD()
407:             . '; message ID: ' . $state->messageID
408:             . '; source URI: ' . $state->sourceURI
409:             . '; target URI: ' . $state->targetURI
410:             . '; user: ' . $state->user
411:             . '; charset: ' . $GLOBALS['backend']->getCharset()
412:             . '; wbxml: ' . ($state->wbxml ? 'yes' : 'no');
413: 
414:         $GLOBALS['backend']->logMessage($str, 'DEBUG');
415:     }
416: 
417:     418: 419: 420: 421: 
422:     public function handleCommand(&$cmd)
423:     {
424:         $name = $cmd->getCommandName();
425:         if ($name != 'Status' && $name != 'Map' && $name != 'Final' &&
426:             $name != 'Sync' && $name != 'Results') {
427:             
428:             $GLOBALS['message_expectresponse'] = true;
429:         }
430:         if ($name == 'Final') {
431:             $this->_gotFinal = true;
432:         }
433:         
434:         $cmd->handleCommand($this->debug);
435:     }
436: 
437:     438: 439: 440: 441: 
442:     public function handleEnd()
443:     {
444:         global $messageFull;
445: 
446:         $state = $GLOBALS['backend']->state;
447: 
448:         449: 
450:         if ($messageFull || $state->hasPendingSyncs()) {
451:             
452:             $GLOBALS['message_expectresponse'] = true;
453:         }
454: 
455:         if (!$messageFull &&
456:             count($p = $state->getPendingSyncs()) > 0) {
457:             foreach ($p as $pendingSync) {
458:                 if (!$messageFull) {
459:                    $GLOBALS['backend']->logMessage(
460:                        'Continuing sync for syncType ' . $pendingSync, 'DEBUG');
461:                     $sync = &$state->getSync($pendingSync);
462:                     $sync->createSyncOutput($this->_xmlWriter);
463:                 }
464:             }
465:         }
466: 
467:         if (isset($state->curSyncItem)) {
468:             $this->_xmlWriter->outputAlert(
469:                 Horde_SyncMl::ALERT_NO_END_OF_DATA,
470:                 $state->curSyncItem->sync->getClientLocURI(),
471:                 $state->curSyncItem->sync->getServerLocURI(),
472:                 $state->curSyncItem->sync->getServerAnchorLast(),
473:                 $state->curSyncItem->sync->getServerAnchorNext());
474:         }
475: 
476:         
477:         if ($this->_gotFinal) {
478:             if (!$messageFull &&
479:                 !$state->hasPendingSyncs()) {
480:                 
481:                 $this->_xmlWriter->outputFinal();
482:                 $GLOBALS['backend']->logMessage('Sending <Final> to client', 'DEBUG');
483:                 $state->delayedFinal = false;
484:             } else {
485:                 $GLOBALS['message_expectresponse'] = true;
486:                 
487:                 $state->delayedFinal = true;
488:             }
489:         } elseif ($state->delayedFinal) {
490:             if (!$messageFull &&
491:                 !$state->hasPendingSyncs()) {
492:                 
493:                 $this->_xmlWriter->outputFinal();
494:                 $GLOBALS['backend']->logMessage(
495:                     'Sending delayed <Final> to client', 'DEBUG');
496:                 $state->delayedFinal = false;
497:             } else {
498:                 $GLOBALS['message_expectresponse'] = true;
499:             }
500:         }
501: 
502:         
503:         $this->_xmlWriter->outputEnd();
504: 
505:         if ($this->_gotFinal &&
506:             !$GLOBALS['message_expectresponse'] &&
507:             $state->isAllSyncsComplete()) {
508:             509: 510: 
511:             foreach ($state->getSyncs() as $sync) {
512:                 $sync->closeSync();
513:             }
514:             $GLOBALS['backend']->logMessage('Session completed and closed', 'DEBUG');
515: 
516:             
517:             $GLOBALS['backend']->sessionClose();
518:         } else {
519:             $GLOBALS['backend']->logMessage('Return message completed', 'DEBUG');
520:         }
521:     }
522: }
523: