source: trunk/zpush/lib/wbxml/wbxmldecoder.php @ 7589

Revision 7589, 20.3 KB checked in by douglas, 11 years ago (diff)

Ticket #3209 - Integrar módulo de sincronização Z-push ao Expresso

Line 
1<?php
2/***********************************************
3* File      :   wbxmldecoder.php
4* Project   :   Z-Push
5* Descr     :   WBXMLDecoder decodes from Wap Binary XML
6*
7* Created   :   01.10.2007
8*
9* Copyright 2007 - 2012 Zarafa Deutschland GmbH
10*
11* This program is free software: you can redistribute it and/or modify
12* it under the terms of the GNU Affero General Public License, version 3,
13* as published by the Free Software Foundation with the following additional
14* term according to sec. 7:
15*
16* According to sec. 7 of the GNU Affero General Public License, version 3,
17* the terms of the AGPL are supplemented with the following terms:
18*
19* "Zarafa" is a registered trademark of Zarafa B.V.
20* "Z-Push" is a registered trademark of Zarafa Deutschland GmbH
21* The licensing of the Program under the AGPL does not imply a trademark license.
22* Therefore any rights, title and interest in our trademarks remain entirely with us.
23*
24* However, if you propagate an unmodified version of the Program you are
25* allowed to use the term "Z-Push" to indicate that you distribute the Program.
26* Furthermore you may use our trademarks where it is necessary to indicate
27* the intended purpose of a product or service provided you use it in accordance
28* with honest practices in industrial or commercial matters.
29* If you want to propagate modified versions of the Program under the name "Z-Push",
30* you may only do so if you have a written permission by Zarafa Deutschland GmbH
31* (to acquire a permission please contact Zarafa at trademark@zarafa.com).
32*
33* This program is distributed in the hope that it will be useful,
34* but WITHOUT ANY WARRANTY; without even the implied warranty of
35* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36* GNU Affero General Public License for more details.
37*
38* You should have received a copy of the GNU Affero General Public License
39* along with this program.  If not, see <http://www.gnu.org/licenses/>.
40*
41* Consult LICENSE file for details
42************************************************/
43
44
45class WBXMLDecoder extends WBXMLDefs {
46    private $in;
47
48    private $version;
49    private $publicid;
50    private $publicstringid;
51    private $charsetid;
52    private $stringtable;
53
54    private $tagcp = 0;
55    private $attrcp = 0;
56
57    private $ungetbuffer;
58
59    private $logStack = array();
60
61    private $inputBuffer = "";
62    private $isWBXML = true;
63
64    const VERSION = 0x03;
65
66    /**
67     * WBXML Decode Constructor
68     *
69     * @param  stream      $input          the incoming data stream
70     *
71     * @access public
72     */
73    public function WBXMLDecoder($input) {
74        // make sure WBXML_DEBUG is defined. It should be at this point
75        if (!defined('WBXML_DEBUG')) define('WBXML_DEBUG', false);
76
77        $this->in = $input;
78
79        $this->readVersion();
80        if (isset($this->version) && $this->version != self::VERSION) {
81            $this->isWBXML = false;
82            return;
83        }
84
85        $this->publicid = $this->getMBUInt();
86        if($this->publicid == 0) {
87            $this->publicstringid = $this->getMBUInt();
88        }
89
90        $this->charsetid = $this->getMBUInt();
91        $this->stringtable = $this->getStringTable();
92    }
93
94    /**
95     * Returns either start, content or end, and auto-concatenates successive content
96     *
97     * @access public
98     * @return element/value
99     */
100    public function getElement() {
101        $element = $this->getToken();
102
103        switch($element[EN_TYPE]) {
104            case EN_TYPE_STARTTAG:
105                return $element;
106            case EN_TYPE_ENDTAG:
107                return $element;
108            case EN_TYPE_CONTENT:
109                while(1) {
110                    $next = $this->getToken();
111                    if($next == false)
112                        return false;
113                    else if($next[EN_TYPE] == EN_CONTENT) {
114                        $element[EN_CONTENT] .= $next[EN_CONTENT];
115                    } else {
116                        $this->ungetElement($next);
117                        break;
118                    }
119                }
120                return $element;
121        }
122
123        return false;
124    }
125
126    /**
127     * Get a peek at the next element
128     *
129     * @access public
130     * @return element
131     */
132    public function peek() {
133        $element = $this->getElement();
134        $this->ungetElement($element);
135        return $element;
136    }
137
138    /**
139     * Get the element of a StartTag
140     *
141     * @param $tag
142     *
143     * @access public
144     * @return element/boolean      returns false if not available
145     */
146    public function getElementStartTag($tag) {
147        $element = $this->getToken();
148
149        if($element[EN_TYPE] == EN_TYPE_STARTTAG && $element[EN_TAG] == $tag)
150            return $element;
151        else {
152            ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementStartTag(): unmatched WBXML tag: '%s' matching '%s' type '%s' flags '%s'", $tag, ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:"")));
153            $this->ungetElement($element);
154        }
155
156        return false;
157    }
158
159    /**
160     * Get the element of a EndTag
161     *
162     * @access public
163     * @return element/boolean      returns false if not available
164     */
165    public function getElementEndTag() {
166        $element = $this->getToken();
167
168        if($element[EN_TYPE] == EN_TYPE_ENDTAG)
169            return $element;
170        else {
171            ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementEndTag(): unmatched WBXML tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:"")));
172
173            $bt = debug_backtrace();
174            ZLog::Write(LOGLEVEL_ERROR, sprintf("WBXMLDecoder->getElementEndTag(): could not read end tag in '%s'. Please enable the LOGLEVEL_WBXML and send the log to the Z-Push dev team.", $bt[0]["file"] . ":" . $bt[0]["line"]));
175
176            // log the remaining wbxml content
177            $this->ungetElement($element);
178            while($el = $this->getElement());
179        }
180
181        return false;
182    }
183
184    /**
185     * Get the content of an element
186     *
187     * @access public
188     * @return string/boolean       returns false if not available
189     */
190    public function getElementContent() {
191        $element = $this->getToken();
192
193        if($element[EN_TYPE] == EN_TYPE_CONTENT) {
194            return $element[EN_CONTENT];
195        }
196        else {
197            ZLog::Write(LOGLEVEL_WBXMLSTACK, sprintf("WBXMLDecoder->getElementContent(): unmatched WBXML content: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:"")));
198            $this->ungetElement($element);
199        }
200
201        return false;
202    }
203
204    /**
205     * 'Ungets' an element writing it into a buffer to be 'get' again
206     *
207     * @param element       $element        the element to get ungetten
208     *
209     * @access public
210     * @return
211     */
212    public function ungetElement($element) {
213        if($this->ungetbuffer)
214            ZLog::Write(LOGLEVEL_ERROR,sprintf("WBXMLDecoder->ungetElement(): WBXML double unget on tag: '%s' type '%s' flags '%s'", ((isset($element[EN_TAG]))?$element[EN_TAG]:""), ((isset($element[EN_TYPE]))?$element[EN_TYPE]:""), ((isset($element[EN_FLAGS]))?$element[EN_FLAGS]:"")));
215
216        $this->ungetbuffer = $element;
217    }
218
219    /**
220     * Returns the plain input stream
221     *
222     * @access public
223     * @return string
224     */
225    public function GetPlainInputStream() {
226        $plain = $this->inputBuffer;
227        while($data = fread($this->in, 4096))
228            $plain .= $data;
229
230        return $plain;
231    }
232
233    /**
234     * Returns if the input is WBXML
235     *
236     * @access public
237     * @return boolean
238     */
239    public function IsWBXML() {
240        return $this->isWBXML;
241    }
242
243
244
245    /**----------------------------------------------------------------------------------------------------------
246     * Private WBXMLDecoder stuff
247     */
248
249    /**
250     * Returns the next token
251     *
252     * @access private
253     * @return token
254     */
255    private function getToken() {
256        // See if there's something in the ungetBuffer
257        if($this->ungetbuffer) {
258            $element = $this->ungetbuffer;
259            $this->ungetbuffer = false;
260            return $element;
261        }
262
263        $el = $this->_getToken();
264        $this->logToken($el);
265
266        return $el;
267    }
268
269    /**
270     * Log the a token to ZLog
271     *
272     * @param string    $el         token
273     *
274     * @access private
275     * @return
276     */
277    private function logToken($el) {
278        if(!WBXML_DEBUG)
279            return;
280
281        $spaces = str_repeat(" ", count($this->logStack));
282
283        switch($el[EN_TYPE]) {
284            case EN_TYPE_STARTTAG:
285                if($el[EN_FLAGS] & EN_FLAGS_CONTENT) {
286                    ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . " <". $el[EN_TAG] . ">");
287                    array_push($this->logStack, $el[EN_TAG]);
288                } else
289                    ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . " <" . $el[EN_TAG] . "/>");
290
291                break;
292            case EN_TYPE_ENDTAG:
293                $tag = array_pop($this->logStack);
294                ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . "</" . $tag . ">");
295                break;
296            case EN_TYPE_CONTENT:
297                ZLog::Write(LOGLEVEL_WBXML,"I " . $spaces . " " . $el[EN_CONTENT]);
298                break;
299        }
300    }
301
302    /**
303     * Returns either a start tag, content or end tag
304     *
305     * @access private
306     * @return
307     */
308    private function _getToken() {
309        // Get the data from the input stream
310        $element = array();
311
312        while(1) {
313            $byte = $this->getByte();
314
315            if(!isset($byte))
316                break;
317
318            switch($byte) {
319                case WBXML_SWITCH_PAGE:
320                    $this->tagcp = $this->getByte();
321                    continue;
322
323                case WBXML_END:
324                    $element[EN_TYPE] = EN_TYPE_ENDTAG;
325                    return $element;
326
327                case WBXML_ENTITY:
328                    $entity = $this->getMBUInt();
329                    $element[EN_TYPE] = EN_TYPE_CONTENT;
330                    $element[EN_CONTENT] = $this->entityToCharset($entity);
331                    return $element;
332
333                case WBXML_STR_I:
334                    $element[EN_TYPE] = EN_TYPE_CONTENT;
335                    $element[EN_CONTENT] = $this->getTermStr();
336                    return $element;
337
338                case WBXML_LITERAL:
339                    $element[EN_TYPE] = EN_TYPE_STARTTAG;
340                    $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
341                    $element[EN_FLAGS] = 0;
342                    return $element;
343
344                case WBXML_EXT_I_0:
345                case WBXML_EXT_I_1:
346                case WBXML_EXT_I_2:
347                    $this->getTermStr();
348                    // Ignore extensions
349                    continue;
350
351                case WBXML_PI:
352                    // Ignore PI
353                    $this->getAttributes();
354                    continue;
355
356                case WBXML_LITERAL_C:
357                    $element[EN_TYPE] = EN_TYPE_STARTTAG;
358                    $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
359                    $element[EN_FLAGS] = EN_FLAGS_CONTENT;
360                    return $element;
361
362                case WBXML_EXT_T_0:
363                case WBXML_EXT_T_1:
364                case WBXML_EXT_T_2:
365                    $this->getMBUInt();
366                    // Ingore extensions;
367                    continue;
368
369                case WBXML_STR_T:
370                    $element[EN_TYPE] = EN_TYPE_CONTENT;
371                    $element[EN_CONTENT] = $this->getStringTableEntry($this->getMBUInt());
372                    return $element;
373
374                case WBXML_LITERAL_A:
375                    $element[EN_TYPE] = EN_TYPE_STARTTAG;
376                    $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
377                    $element[EN_ATTRIBUTES] = $this->getAttributes();
378                    $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES;
379                    return $element;
380                case WBXML_EXT_0:
381                case WBXML_EXT_1:
382                case WBXML_EXT_2:
383                    continue;
384
385                case WBXML_OPAQUE:
386                    $length = $this->getMBUInt();
387                    $element[EN_TYPE] = EN_TYPE_CONTENT;
388                    $element[EN_CONTENT] = $this->getOpaque($length);
389                    return $element;
390
391                case WBXML_LITERAL_AC:
392                    $element[EN_TYPE] = EN_TYPE_STARTTAG;
393                    $element[EN_TAG] = $this->getStringTableEntry($this->getMBUInt());
394                    $element[EN_ATTRIBUTES] = $this->getAttributes();
395                    $element[EN_FLAGS] = EN_FLAGS_ATTRIBUTES | EN_FLAGS_CONTENT;
396                    return $element;
397
398                default:
399                    $element[EN_TYPE] = EN_TYPE_STARTTAG;
400                    $element[EN_TAG] = $this->getMapping($this->tagcp, $byte & 0x3f);
401                    $element[EN_FLAGS] = ($byte & 0x80 ? EN_FLAGS_ATTRIBUTES : 0) | ($byte & 0x40 ? EN_FLAGS_CONTENT : 0);
402                    if($byte & 0x80)
403                        $element[EN_ATTRIBUTES] = $this->getAttributes();
404                    return $element;
405            }
406        }
407    }
408
409    /**
410     * Gets attributes
411     *
412     * @access private
413     * @return
414     */
415    private function getAttributes() {
416        $attributes = array();
417        $attr = "";
418
419        while(1) {
420            $byte = $this->getByte();
421
422            if(count($byte) == 0)
423                break;
424
425            switch($byte) {
426                case WBXML_SWITCH_PAGE:
427                    $this->attrcp = $this->getByte();
428                    break;
429
430                case WBXML_END:
431                    if($attr != "")
432                        $attributes += $this->splitAttribute($attr);
433
434                    return $attributes;
435
436                case WBXML_ENTITY:
437                    $entity = $this->getMBUInt();
438                    $attr .= $this->entityToCharset($entity);
439                    return $attr; /* fmbiete's contribution r1534, ZP-324 */
440
441                case WBXML_STR_I:
442                    $attr .= $this->getTermStr();
443                    return $attr; /* fmbiete's contribution r1534, ZP-324 */
444
445                case WBXML_LITERAL:
446                    if($attr != "")
447                        $attributes += $this->splitAttribute($attr);
448
449                    $attr = $this->getStringTableEntry($this->getMBUInt());
450                    return $attr; /* fmbiete's contribution r1534, ZP-324 */
451
452                case WBXML_EXT_I_0:
453                case WBXML_EXT_I_1:
454                case WBXML_EXT_I_2:
455                    $this->getTermStr();
456                    continue;
457
458                case WBXML_PI:
459                case WBXML_LITERAL_C:
460                    // Invalid
461                    return false;
462
463                case WBXML_EXT_T_0:
464                case WBXML_EXT_T_1:
465                case WBXML_EXT_T_2:
466                    $this->getMBUInt();
467                    continue;
468
469                case WBXML_STR_T:
470                    $attr .= $this->getStringTableEntry($this->getMBUInt());
471                    return $attr; /* fmbiete's contribution r1534, ZP-324 */
472
473                case WBXML_LITERAL_A:
474                    return false;
475
476                case WBXML_EXT_0:
477                case WBXML_EXT_1:
478                case WBXML_EXT_2:
479                    continue;
480
481                case WBXML_OPAQUE:
482                    $length = $this->getMBUInt();
483                    $attr .= $this->getOpaque($length);
484                    return $attr; /* fmbiete's contribution r1534, ZP-324 */
485
486                case WBXML_LITERAL_AC:
487                    return false;
488
489                default:
490                    if($byte < 128) {
491                        if($attr != "") {
492                            $attributes += $this->splitAttribute($attr);
493                            $attr = "";
494                        }
495                    }
496                    $attr .= $this->getMapping($this->attrcp, $byte);
497                    break;
498            }
499        }
500    }
501
502    /**
503     * Splits an attribute
504     *
505     * @param string $attr     attribute to be splitted
506     *
507     * @access private
508     * @return array
509     */
510    private function splitAttribute($attr) {
511        $attributes = array();
512
513        $pos = strpos($attr,chr(61)); // equals sign
514
515        if($pos)
516            $attributes[substr($attr, 0, $pos)] = substr($attr, $pos+1);
517        else
518            $attributes[$attr] = null;
519
520        return $attributes;
521    }
522
523    /**
524     * Reads from the stream until getting a string terminator
525     *
526     * @access private
527     * @return string
528     */
529    private function getTermStr() {
530        $str = "";
531        while(1) {
532            $in = $this->getByte();
533
534            if($in == 0)
535                break;
536            else
537                $str .= chr($in);
538        }
539
540        return $str;
541    }
542
543    /**
544     * Reads $len from the input stream
545     *
546     * @param int   $len
547     *
548     * @access private
549     * @return string
550     */
551    private function getOpaque($len) {
552        // TODO check if it's possible to do it other way
553        // fread stops reading because the following condition is true (from php.net):
554        // if the stream is read buffered and it does not represent a plain file,
555        // at most one read of up to a number of bytes equal to the chunk size
556        // (usually 8192) is made; depending on the previously buffered data,
557        // the size of the returned data may be larger than the chunk size.
558
559        // using only return fread it will return only a part of stream if chunk is smaller
560        // than $len. Read from stream in a loop until the $len is reached.
561        $d = "";
562        $l = 0;
563        while (1) {
564            $l = (($len - strlen($d)) > 8192) ? 8192 : ($len - strlen($d));
565            if ($l > 0) {
566                $data = fread($this->in, $l);
567
568                // Stream ends prematurely on instable connections and big mails
569                if ($data === false || feof($this->in))
570                    throw new HTTPReturnCodeException(sprintf("WBXMLDecoder->getOpaque() connection unavailable while trying to read %d bytes from stream. Aborting after %d bytes read.", $len, strlen($d)), HTTP_CODE_500, null, LOGLEVEL_WARN);
571                else
572                    $d .= $data;
573            }
574            if (strlen($d) >= $len) break;
575        }
576        return $d;
577    }
578
579    /**
580     * Reads one byte from the input stream
581     *
582     * @access private
583     * @return int
584     */
585    private function getByte() {
586        $ch = fread($this->in, 1);
587        if(strlen($ch) > 0)
588            return ord($ch);
589        else
590            return;
591    }
592
593    /**
594     * Reads string length from the input stream
595     *
596     * @access private
597     * @return
598     */
599    private function getMBUInt() {
600        $uint = 0;
601
602        while(1) {
603          $byte = $this->getByte();
604
605          $uint |= $byte & 0x7f;
606
607          if($byte & 0x80)
608              $uint = $uint << 7;
609          else
610              break;
611        }
612
613        return $uint;
614    }
615
616    /**
617     * Reads string table from the input stream
618     *
619     * @access private
620     * @return int
621     */
622    private function getStringTable() {
623        $stringtable = "";
624
625        $length = $this->getMBUInt();
626        if($length > 0)
627            $stringtable = fread($this->in, $length);
628
629        return $stringtable;
630    }
631
632    /**
633     * Returns the mapping for a specified codepage and id
634     *
635     * @param $cp   codepage
636     * @param $id
637     *
638     * @access public
639     * @return string
640     */
641    private function getMapping($cp, $id) {
642        if(!isset($this->dtd["codes"][$cp]) || !isset($this->dtd["codes"][$cp][$id]))
643            return false;
644        else {
645            if(isset($this->dtd["namespaces"][$cp])) {
646                return $this->dtd["namespaces"][$cp] . ":" . $this->dtd["codes"][$cp][$id];
647            } else
648                return $this->dtd["codes"][$cp][$id];
649        }
650    }
651
652    /**
653     * Reads one byte from the input stream
654     *
655     * @access private
656     * @return void
657     */
658    private function readVersion() {
659        $ch = $this->getByte();
660
661        if($ch != NULL) {
662            $this->inputBuffer .= chr($ch);
663            $this->version = $ch;
664        }
665    }
666}
667
668?>
Note: See TracBrowser for help on using the repository browser.