Overview

Packages

  • ActiveSync
  • None

Classes

  • Horde_ActiveSync
  • Horde_ActiveSync_Connector_Exporter
  • Horde_ActiveSync_Connector_Importer
  • Horde_ActiveSync_Driver_Base
  • Horde_ActiveSync_Exception
  • Horde_ActiveSync_Exception_InvalidRequest
  • Horde_ActiveSync_Exception_StateGone
  • Horde_ActiveSync_Message_Base
  • Horde_ActiveSync_Request_Base
  • Horde_ActiveSync_Request_FolderCreate
  • Horde_ActiveSync_Request_FolderSync
  • Horde_ActiveSync_Request_GetHierarchy
  • Horde_ActiveSync_Request_GetItemEstimate
  • Horde_ActiveSync_Request_MeetingResponse
  • Horde_ActiveSync_Request_MoveItems
  • Horde_ActiveSync_Request_Notify
  • Horde_ActiveSync_Request_Ping
  • Horde_ActiveSync_Request_Provision
  • Horde_ActiveSync_Request_Search
  • Horde_ActiveSync_Request_SendMail
  • Horde_ActiveSync_Request_SmartForward
  • Horde_ActiveSync_Request_SmartReply
  • Horde_ActiveSync_Request_Sync
  • Horde_ActiveSync_State_File
  • Horde_ActiveSync_Sync
  • Horde_ActiveSync_Wbxml
  • Horde_ActiveSync_Wbxml_Decoder
  • Horde_ActiveSync_Wbxml_Encoder
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: /**
  3:  * ActiveSync specific WBXML encoding. No need to build xml document in memory
  4:  * since we stream the actual binary data as we build it. Contains code from
  5:  * the Z-Push project. Original file header below.
  6:  *
  7:  * Copyright 2010-2012 Horde LLC (http://www.horde.org/)
  8:  *
  9:  * @author Michael J. Rubinsky <mrubinsk@horde.org>
 10:  * @package ActiveSync
 11:  */
 12: 
 13: /**
 14:  * File      :   wbxml.php
 15:  * Project   :   Z-Push
 16:  * Descr     :   WBXML mapping file
 17:  *
 18:  * Created   :   01.10.2007
 19:  *
 20:  * © Zarafa Deutschland GmbH, www.zarafaserver.de
 21:  * This file is distributed under GPL-2.0.
 22:  * Consult COPYING file for details
 23:  */
 24: class Horde_ActiveSync_Wbxml_Encoder extends Horde_ActiveSync_Wbxml
 25: {
 26:     /**
 27:      * Cache the tags to output. The stack is output when content() is called.
 28:      * We only output tags when they actually contain something. i.e. calling
 29:      * startTag() 10 times, then endTag() will cause 0 bytes of output apart
 30:      * from the header.
 31:      *
 32:      * @var array
 33:      */
 34:     private $_stack = array();
 35: 
 36:     /**
 37:      * Const'r
 38:      *
 39:      * @param stream $output
 40:      *
 41:      * @return Horde_ActiveSync_Wbxml_Encoder
 42:      */
 43:     function __construct($output)
 44:     {
 45:         parent::__construct($output);
 46: 
 47:         /* reverse-map the DTD */
 48:         $dtd = array();
 49:         foreach ($this->_dtd['namespaces'] as $nsid => $nsname) {
 50:             $dtd['namespaces'][$nsname] = $nsid;
 51:         }
 52: 
 53:         foreach ($this->_dtd['codes'] as $cp => $value) {
 54:             $dtd['codes'][$cp] = array();
 55:             foreach ($this->_dtd['codes'][$cp] as $tagid => $tagname) {
 56:                 $dtd['codes'][$cp][$tagname] = $tagid;
 57:             }
 58:         }
 59:         $this->_dtd = $dtd;
 60:     }
 61: 
 62:     /**
 63:      * Setter for the logger
 64:      *
 65:      * @param Horde_Log_Logger $logger  The logger instance
 66:      */
 67:     public function setLogger(Horde_Log_Logger $logger)
 68:     {
 69:         $this->_logger = $logger;
 70:     }
 71: 
 72:     /**
 73:      * Starts the wbxml output
 74:      *
 75:      * @return void
 76:      */
 77:     public function startWBXML()
 78:     {
 79:         header('Content-Type: application/vnd.ms-sync.wbxml');
 80:         $this->outputWbxmlHeader();
 81:     }
 82: 
 83:     public function outputWbxmlHeader()
 84:     {
 85:         $this->_outByte(0x03);   // WBXML 1.3
 86:         $this->_outMBUInt(0x01); // Public ID 1
 87:         $this->_outMBUInt(106);  // UTF-8
 88:         $this->_outMBUInt(0x00); // string table length (0)
 89:     }
 90: 
 91:     /**
 92:      * Start output for the specified tag
 93:      *
 94:      * @param string $tag            The name of the tag to start
 95:      * @param mixed $attributes     Any attributes for the start tag
 96:      * @param boolean $output_empty  Force output of empty tags
 97:      *
 98:      * @return void
 99:      */
100:     public function startTag($tag, $attributes = false, $output_empty = false)
101:     {
102:         $stackelem = array();
103:         if (!$output_empty) {
104:             $stackelem['tag'] = $tag;
105:             $stackelem['attributes'] = $attributes;
106:             $stackelem['nocontent'] = $output_empty;
107:             $stackelem['sent'] = false;
108:             array_push($this->_stack, $stackelem);
109:         } else {
110:             /* Flush the stack if we want to force empty tags */
111:             $this->_outputStack();
112:             $this->_startTag($tag, $attributes, $output_empty);
113:         }
114:     }
115: 
116:     /**
117:      * Output the end tag
118:      *
119:      * @return void
120:      */
121:     public function endTag()
122:     {
123:         $stackelem = array_pop($this->_stack);
124:         /* Only output end tags for items that have had a start tag sent */
125:         if ($stackelem['sent']) {
126:             $this->_endTag();
127:         }
128:     }
129: 
130:     /**
131:      * Output the tag content
132:      *
133:      * @param string $content  The value to output for this tag
134:      *
135:      * @return void
136:      */
137:     public function content($content)
138:     {
139:         /* Filter out \0 since it's a string terminator in wbxml. We cannot send
140:          * \0 within the xml content */
141:         $content = str_replace('\0', '', $content);
142:         if ('x' . $content == 'x') {
143:             return;
144:         }
145:         $this->_outputStack();
146:         $this->_content($content);
147:     }
148: 
149:     /**
150:      * Output any tags on the stack that haven't been output yet
151:      *
152:      * @return void
153:      */
154:     private function _outputStack()
155:     {
156:         for ($i=0; $i < count($this->_stack); $i++) {
157:             if (!$this->_stack[$i]['sent']) {
158:                 $this->_startTag($this->_stack[$i]['tag'], $this->_stack[$i]['attributes'], $this->_stack[$i]['nocontent']);
159:                 $this->_stack[$i]['sent'] = true;
160:             }
161:         }
162:     }
163: 
164:     /**
165:      * Actually outputs the start tag
166:      *
167:      * @param string $tag @see Horde_ActiveSync_Wbxml_Encoder::startTag
168:      * @param mixed $attributes @see Horde_ActiveSync_Wbxml_Encoder::startTag
169:      * @param boolean $output_empty @see Horde_ActiveSync_Wbxml_Encoder::startTag
170:      *
171:      * @return void
172:      */
173:     private function _startTag($tag, $attributes = false, $output_empty = false)
174:     {
175:         $this->_logStartTag($tag, $attributes, $output_empty);
176:         $mapping = $this->_getMapping($tag);
177:         if (!$mapping) {
178:            return false;
179:         }
180: 
181:         /* Make sure we don't need to switch code pages */
182:         if ($this->_tagcp != $mapping['cp']) {
183:             $this->_outSwitchPage($mapping['cp']);
184:             $this->_tagcp = $mapping['cp'];
185:         }
186: 
187:         /* Build and send the code */
188:         $code = $mapping['code'];
189:         if (isset($attributes) && is_array($attributes) && count($attributes) > 0) {
190:             $code |= 0x80;
191:         } elseif (!isset($output_empty) || !$output_empty) {
192:             $code |= 0x40;
193:         }
194:         $this->_outByte($code);
195:         if ($code & 0x80) {
196:             $this->_outAttributes($attributes);
197:         }
198:     }
199: 
200:     /**
201:      * Outputs data
202:      *
203:      * @param string $content  The content to send
204:      *
205:      * @return void
206:      */
207:     private function _content($content)
208:     {
209:         $this->_logContent($content);
210:         $this->_outByte(Horde_ActiveSync_Wbxml::STR_I);
211:         $this->_outTermStr($content);
212:     }
213: 
214:     /**
215:      * Output the endtag
216:      *
217:      * @return void
218:      */
219:     function _endTag() {
220:         $this->_logEndTag();
221:         $this->_outByte(Horde_ActiveSync_Wbxml::END);
222:     }
223: 
224:     /**
225:      * Output a single byte to the stream
226:      *
227:      * @param byte $byte
228:      * @return unknown_type
229:      */
230:     private function _outByte($byte)
231:     {
232:         fwrite($this->_stream, chr($byte));
233:     }
234: 
235:     /**
236:      * Outputs an MBUInt to the stream
237:      * @param $uint
238:      * @return unknown_type
239:      */
240:     private function _outMBUInt($uint)
241:     {
242:         while (1) {
243:             $byte = $uint & 0x7f;
244:             $uint = $uint >> 7;
245:             if ($uint == 0) {
246:                 $this->_outByte($byte);
247:                 break;
248:             } else {
249:                 $this->_outByte($byte | 0x80);
250:             }
251:         }
252:     }
253: 
254:     /**
255:      * Output a string along with the terminator.
256:      *
257:      * @param string $content  The string
258:      *
259:      * @return void
260:      */
261:     private function _outTermStr($content)
262:     {
263:         fwrite($this->_stream, $content);
264:         fwrite($this->_stream, chr(0));
265:     }
266: 
267:     /**
268:      * Output attributes
269:      *
270:      * @return void
271:      */
272:     private function _outAttributes()
273:     {
274:         // We don't actually support this, because to do so, we would have
275:         // to build a string table before sending the data (but we can't
276:         // because we're streaming), so we'll just send an END, which just
277:         // terminates the attribute list with 0 attributes.
278:         $this->_outByte(Horde_ActiveSync_Wbxml::END);
279:     }
280: 
281:     /**
282:      *
283:      * @param $page
284:      * @return unknown_type
285:      */
286:     private function _outSwitchPage($page)
287:     {
288:         $this->_outByte(Horde_ActiveSync_Wbxml::SWITCH_PAGE);
289:         $this->_outByte($page);
290:     }
291: 
292:     /**
293:      * Obtain the wbxml mapping for the given tag
294:      *
295:      * @param string $tag
296:      *
297:      * @return array
298:      */
299:     private function _getMapping($tag)
300:     {
301:         $mapping = array();
302:         $split = $this->_splitTag($tag);
303:         if (isset($split['ns'])) {
304:             $cp = $this->_dtd['namespaces'][$split['ns']];
305:         } else {
306:             $cp = 0;
307:         }
308: 
309:         $code = $this->_dtd['codes'][$cp][$split['tag']];
310:         $mapping['cp'] = $cp;
311:         $mapping['code'] = $code;
312: 
313:         return $mapping;
314:     }
315: 
316:     /**
317:      * Split a tag into it's atomic parts
318:      *
319:      * @param string $fulltag  The full tag name
320:      *                         (e.g. POOMCONTACTS:Email1Address)
321:      *
322:      * @return array  An array containing the namespace and tagname
323:      */
324:     private function _splitTag($fulltag)
325:     {
326:         $ns = false;
327:         $pos = strpos($fulltag, chr(58)); // chr(58) == ':'
328:         if ($pos) {
329:             $ns = substr($fulltag, 0, $pos);
330:             $tag = substr($fulltag, $pos+1);
331:         } else {
332:             $tag = $fulltag;
333:         }
334: 
335:         $ret = array();
336:         if ($ns) {
337:             $ret['ns'] = $ns;
338:         }
339:         $ret['tag'] = $tag;
340: 
341:         return $ret;
342:     }
343: 
344:     /**
345:      * Log the start tag output
346:      *
347:      * @param string $tag
348:      * @param mixed $attr
349:      * @param boolean $output_empty
350:      *
351:      * @return void
352:      */
353:     private function _logStartTag($tag, $attr, $output_empty)
354:     {
355:         $spaces = str_repeat(' ', count($this->_logStack));
356:         if ($output_empty) {
357:             $this->_logger->debug(sprintf('O %s <%s/>', $spaces, $tag));
358:         } else {
359:             array_push($this->_logStack, $tag);
360:             $this->_logger->debug(sprintf('O %s <%s>', $spaces, $tag));
361:         }
362:     }
363: 
364:     /**
365:      * Log the endtag output
366:      *
367:      * @return void
368:      */
369:     private function _logEndTag()
370:     {
371:         $spaces = str_repeat(' ', count($this->_logStack) - 1);
372:         $tag = array_pop($this->_logStack);
373:         $this->_logger->debug(sprintf('O %s <%s/>', $spaces, $tag));
374:     }
375: 
376:     /**
377:      * Log the content output
378:      *
379:      * @param string $content  The output
380:      *
381:      * @return void
382:      */
383:     private function _logContent($content)
384:     {
385:         $spaces = str_repeat(' ', count($this->_logStack) + 1);
386:         $this->_logger->debug('O ' . $spaces . $content);
387:     }
388: 
389: }
API documentation generated by ApiGen