1<?php
2
3/*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 *   http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 *
21 * @package thrift.protocol
22 */
23
24namespace Thrift\Protocol;
25
26use Thrift\Type\TType;
27use Thrift\Exception\TProtocolException;
28use Thrift\Protocol\JSON\BaseContext;
29use Thrift\Protocol\JSON\LookaheadReader;
30use Thrift\Protocol\JSON\PairContext;
31use Thrift\Protocol\JSON\ListContext;
32
33/**
34 * JSON implementation of thrift protocol, ported from Java.
35 */
36class TJSONProtocol extends TProtocol
37{
38    const COMMA = ',';
39    const COLON = ':';
40    const LBRACE = '{';
41    const RBRACE = '}';
42    const LBRACKET = '[';
43    const RBRACKET = ']';
44    const QUOTE = '"';
45    const BACKSLASH = '\\';
46    const ZERO = '0';
47    const ESCSEQ = '\\';
48    const DOUBLEESC = '__DOUBLE_ESCAPE_SEQUENCE__';
49
50    const VERSION = 1;
51
52    public static $JSON_CHAR_TABLE = array(
53        /*  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F */
54        0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, // 0
55        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
56        1, 1, '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
57    );
58
59    public static $ESCAPE_CHARS = array('"', '\\', '/', "b", "f", "n", "r", "t");
60
61    public static $ESCAPE_CHAR_VALS = array(
62        '"', '\\', '/', "\x08", "\f", "\n", "\r", "\t",
63    );
64
65    const NAME_BOOL = "tf";
66    const NAME_BYTE = "i8";
67    const NAME_I16 = "i16";
68    const NAME_I32 = "i32";
69    const NAME_I64 = "i64";
70    const NAME_DOUBLE = "dbl";
71    const NAME_STRUCT = "rec";
72    const NAME_STRING = "str";
73    const NAME_MAP = "map";
74    const NAME_LIST = "lst";
75    const NAME_SET = "set";
76
77    private function getTypeNameForTypeID($typeID)
78    {
79        switch ($typeID) {
80            case TType::BOOL:
81                return self::NAME_BOOL;
82            case TType::BYTE:
83                return self::NAME_BYTE;
84            case TType::I16:
85                return self::NAME_I16;
86            case TType::I32:
87                return self::NAME_I32;
88            case TType::I64:
89                return self::NAME_I64;
90            case TType::DOUBLE:
91                return self::NAME_DOUBLE;
92            case TType::STRING:
93                return self::NAME_STRING;
94            case TType::STRUCT:
95                return self::NAME_STRUCT;
96            case TType::MAP:
97                return self::NAME_MAP;
98            case TType::SET:
99                return self::NAME_SET;
100            case TType::LST:
101                return self::NAME_LIST;
102            default:
103                throw new TProtocolException("Unrecognized type", TProtocolException::UNKNOWN);
104        }
105    }
106
107    private function getTypeIDForTypeName($name)
108    {
109        $result = TType::STOP;
110
111        if (strlen($name) > 1) {
112            switch (substr($name, 0, 1)) {
113                case 'd':
114                    $result = TType::DOUBLE;
115                    break;
116                case 'i':
117                    switch (substr($name, 1, 1)) {
118                        case '8':
119                            $result = TType::BYTE;
120                            break;
121                        case '1':
122                            $result = TType::I16;
123                            break;
124                        case '3':
125                            $result = TType::I32;
126                            break;
127                        case '6':
128                            $result = TType::I64;
129                            break;
130                    }
131                    break;
132                case 'l':
133                    $result = TType::LST;
134                    break;
135                case 'm':
136                    $result = TType::MAP;
137                    break;
138                case 'r':
139                    $result = TType::STRUCT;
140                    break;
141                case 's':
142                    if (substr($name, 1, 1) == 't') {
143                        $result = TType::STRING;
144                    } elseif (substr($name, 1, 1) == 'e') {
145                        $result = TType::SET;
146                    }
147                    break;
148                case 't':
149                    $result = TType::BOOL;
150                    break;
151            }
152        }
153        if ($result == TType::STOP) {
154            throw new TProtocolException("Unrecognized type", TProtocolException::INVALID_DATA);
155        }
156
157        return $result;
158    }
159
160    public $contextStack_ = array();
161    public $context_;
162    public $reader_;
163
164    private function pushContext($c)
165    {
166        array_push($this->contextStack_, $this->context_);
167        $this->context_ = $c;
168    }
169
170    private function popContext()
171    {
172        $this->context_ = array_pop($this->contextStack_);
173    }
174
175    public function __construct($trans)
176    {
177        parent::__construct($trans);
178        $this->context_ = new BaseContext();
179        $this->reader_ = new LookaheadReader($this);
180    }
181
182    public function reset()
183    {
184        $this->contextStack_ = array();
185        $this->context_ = new BaseContext();
186        $this->reader_ = new LookaheadReader($this);
187    }
188
189    private $tmpbuf_ = array(4);
190
191    public function readJSONSyntaxChar($b)
192    {
193        $ch = $this->reader_->read();
194
195        if (substr($ch, 0, 1) != $b) {
196            throw new TProtocolException("Unexpected character: " . $ch, TProtocolException::INVALID_DATA);
197        }
198    }
199
200    private function hexVal($s)
201    {
202        for ($i = 0; $i < strlen($s); $i++) {
203            $ch = substr($s, $i, 1);
204
205            if (!($ch >= "a" && $ch <= "f") && !($ch >= "0" && $ch <= "9")) {
206                throw new TProtocolException("Expected hex character " . $ch, TProtocolException::INVALID_DATA);
207            }
208        }
209
210        return hexdec($s);
211    }
212
213    private function hexChar($val)
214    {
215        return dechex($val);
216    }
217
218    private function hasJSONUnescapedUnicode()
219    {
220        if (PHP_MAJOR_VERSION > 5 || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 4)) {
221            return true;
222        }
223
224        return false;
225    }
226
227    private function unescapedUnicode($str)
228    {
229        if ($this->hasJSONUnescapedUnicode()) {
230            return json_encode($str, JSON_UNESCAPED_UNICODE);
231        }
232
233        $json = json_encode($str);
234
235        /*
236         * Unescaped character outside the Basic Multilingual Plane
237         * High surrogate: 0xD800 - 0xDBFF
238         * Low surrogate: 0xDC00 - 0xDFFF
239         */
240        $json = preg_replace_callback(
241            '/\\\\u(d[89ab][0-9a-f]{2})\\\\u(d[cdef][0-9a-f]{2})/i',
242            function ($matches) {
243                return mb_convert_encoding(pack('H*', $matches[1] . $matches[2]), 'UTF-8', 'UTF-16BE');
244            },
245            $json
246        );
247
248        /*
249         * Unescaped characters within the Basic Multilingual Plane
250         */
251        $json = preg_replace_callback(
252            '/\\\\u([0-9a-f]{4})/i',
253            function ($matches) {
254                return mb_convert_encoding(pack('H*', $matches[1]), 'UTF-8', 'UTF-16BE');
255            },
256            $json
257        );
258
259        return $json;
260    }
261
262    private function writeJSONString($b)
263    {
264        $this->context_->write();
265
266        if (is_numeric($b) && $this->context_->escapeNum()) {
267            $this->trans_->write(self::QUOTE);
268        }
269
270        $this->trans_->write($this->unescapedUnicode($b));
271
272        if (is_numeric($b) && $this->context_->escapeNum()) {
273            $this->trans_->write(self::QUOTE);
274        }
275    }
276
277    private function writeJSONInteger($num)
278    {
279        $this->context_->write();
280
281        if ($this->context_->escapeNum()) {
282            $this->trans_->write(self::QUOTE);
283        }
284
285        $this->trans_->write($num);
286
287        if ($this->context_->escapeNum()) {
288            $this->trans_->write(self::QUOTE);
289        }
290    }
291
292    private function writeJSONDouble($num)
293    {
294        $this->context_->write();
295
296        if ($this->context_->escapeNum()) {
297            $this->trans_->write(self::QUOTE);
298        }
299
300        $this->trans_->write(json_encode($num));
301
302        if ($this->context_->escapeNum()) {
303            $this->trans_->write(self::QUOTE);
304        }
305    }
306
307    private function writeJSONBase64($data)
308    {
309        $this->context_->write();
310        $this->trans_->write(self::QUOTE);
311        $this->trans_->write(json_encode(base64_encode($data)));
312        $this->trans_->write(self::QUOTE);
313    }
314
315    private function writeJSONObjectStart()
316    {
317        $this->context_->write();
318        $this->trans_->write(self::LBRACE);
319        $this->pushContext(new PairContext($this));
320    }
321
322    private function writeJSONObjectEnd()
323    {
324        $this->popContext();
325        $this->trans_->write(self::RBRACE);
326    }
327
328    private function writeJSONArrayStart()
329    {
330        $this->context_->write();
331        $this->trans_->write(self::LBRACKET);
332        $this->pushContext(new ListContext($this));
333    }
334
335    private function writeJSONArrayEnd()
336    {
337        $this->popContext();
338        $this->trans_->write(self::RBRACKET);
339    }
340
341    private function readJSONString($skipContext)
342    {
343        if (!$skipContext) {
344            $this->context_->read();
345        }
346
347        $jsonString = '';
348        $lastChar = null;
349        while (true) {
350            $ch = $this->reader_->read();
351            $jsonString .= $ch;
352            if ($ch == self::QUOTE &&
353                $lastChar !== null &&
354                $lastChar !== self::ESCSEQ) {
355                break;
356            }
357            if ($ch == self::ESCSEQ && $lastChar == self::ESCSEQ) {
358                $lastChar = self::DOUBLEESC;
359            } else {
360                $lastChar = $ch;
361            }
362        }
363
364        return json_decode($jsonString);
365    }
366
367    private function isJSONNumeric($b)
368    {
369        switch ($b) {
370            case '+':
371            case '-':
372            case '.':
373            case '0':
374            case '1':
375            case '2':
376            case '3':
377            case '4':
378            case '5':
379            case '6':
380            case '7':
381            case '8':
382            case '9':
383            case 'E':
384            case 'e':
385                return true;
386        }
387
388        return false;
389    }
390
391    private function readJSONNumericChars()
392    {
393        $strbld = array();
394
395        while (true) {
396            $ch = $this->reader_->peek();
397
398            if (!$this->isJSONNumeric($ch)) {
399                break;
400            }
401
402            $strbld[] = $this->reader_->read();
403        }
404
405        return implode("", $strbld);
406    }
407
408    private function readJSONInteger()
409    {
410        $this->context_->read();
411
412        if ($this->context_->escapeNum()) {
413            $this->readJSONSyntaxChar(self::QUOTE);
414        }
415
416        $str = $this->readJSONNumericChars();
417
418        if ($this->context_->escapeNum()) {
419            $this->readJSONSyntaxChar(self::QUOTE);
420        }
421
422        if (!is_numeric($str)) {
423            throw new TProtocolException("Invalid data in numeric: " . $str, TProtocolException::INVALID_DATA);
424        }
425
426        return intval($str);
427    }
428
429    /**
430     * Identical to readJSONInteger but without the final cast.
431     * Needed for proper handling of i64 on 32 bit machines.  Why a
432     * separate function?  So we don't have to force the rest of the
433     * use cases through the extra conditional.
434     */
435    private function readJSONIntegerAsString()
436    {
437        $this->context_->read();
438
439        if ($this->context_->escapeNum()) {
440            $this->readJSONSyntaxChar(self::QUOTE);
441        }
442
443        $str = $this->readJSONNumericChars();
444
445        if ($this->context_->escapeNum()) {
446            $this->readJSONSyntaxChar(self::QUOTE);
447        }
448
449        if (!is_numeric($str)) {
450            throw new TProtocolException("Invalid data in numeric: " . $str, TProtocolException::INVALID_DATA);
451        }
452
453        return $str;
454    }
455
456    private function readJSONDouble()
457    {
458        $this->context_->read();
459
460        if (substr($this->reader_->peek(), 0, 1) == self::QUOTE) {
461            $arr = $this->readJSONString(true);
462
463            if ($arr == "NaN") {
464                return NAN;
465            } elseif ($arr == "Infinity") {
466                return INF;
467            } elseif (!$this->context_->escapeNum()) {
468                throw new TProtocolException(
469                    "Numeric data unexpectedly quoted " . $arr,
470                    TProtocolException::INVALID_DATA
471                );
472            }
473
474            return floatval($arr);
475        } else {
476            if ($this->context_->escapeNum()) {
477                $this->readJSONSyntaxChar(self::QUOTE);
478            }
479
480            return floatval($this->readJSONNumericChars());
481        }
482    }
483
484    private function readJSONBase64()
485    {
486        $arr = $this->readJSONString(false);
487        $data = base64_decode($arr, true);
488
489        if ($data === false) {
490            throw new TProtocolException("Invalid base64 data " . $arr, TProtocolException::INVALID_DATA);
491        }
492
493        return $data;
494    }
495
496    private function readJSONObjectStart()
497    {
498        $this->context_->read();
499        $this->readJSONSyntaxChar(self::LBRACE);
500        $this->pushContext(new PairContext($this));
501    }
502
503    private function readJSONObjectEnd()
504    {
505        $this->readJSONSyntaxChar(self::RBRACE);
506        $this->popContext();
507    }
508
509    private function readJSONArrayStart()
510    {
511        $this->context_->read();
512        $this->readJSONSyntaxChar(self::LBRACKET);
513        $this->pushContext(new ListContext($this));
514    }
515
516    private function readJSONArrayEnd()
517    {
518        $this->readJSONSyntaxChar(self::RBRACKET);
519        $this->popContext();
520    }
521
522    /**
523     * Writes the message header
524     *
525     * @param string $name Function name
526     * @param int $type message type TMessageType::CALL or TMessageType::REPLY
527     * @param int $seqid The sequence id of this message
528     */
529    public function writeMessageBegin($name, $type, $seqid)
530    {
531        $this->writeJSONArrayStart();
532        $this->writeJSONInteger(self::VERSION);
533        $this->writeJSONString($name);
534        $this->writeJSONInteger($type);
535        $this->writeJSONInteger($seqid);
536    }
537
538    /**
539     * Close the message
540     */
541    public function writeMessageEnd()
542    {
543        $this->writeJSONArrayEnd();
544    }
545
546    /**
547     * Writes a struct header.
548     *
549     * @param  string $name Struct name
550     * @throws TException on write error
551     * @return int        How many bytes written
552     */
553    public function writeStructBegin($name)
554    {
555        $this->writeJSONObjectStart();
556    }
557
558    /**
559     * Close a struct.
560     *
561     * @throws TException on write error
562     * @return int        How many bytes written
563     */
564    public function writeStructEnd()
565    {
566        $this->writeJSONObjectEnd();
567    }
568
569    public function writeFieldBegin($fieldName, $fieldType, $fieldId)
570    {
571        $this->writeJSONInteger($fieldId);
572        $this->writeJSONObjectStart();
573        $this->writeJSONString($this->getTypeNameForTypeID($fieldType));
574    }
575
576    public function writeFieldEnd()
577    {
578        $this->writeJsonObjectEnd();
579    }
580
581    public function writeFieldStop()
582    {
583    }
584
585    public function writeMapBegin($keyType, $valType, $size)
586    {
587        $this->writeJSONArrayStart();
588        $this->writeJSONString($this->getTypeNameForTypeID($keyType));
589        $this->writeJSONString($this->getTypeNameForTypeID($valType));
590        $this->writeJSONInteger($size);
591        $this->writeJSONObjectStart();
592    }
593
594    public function writeMapEnd()
595    {
596        $this->writeJSONObjectEnd();
597        $this->writeJSONArrayEnd();
598    }
599
600    public function writeListBegin($elemType, $size)
601    {
602        $this->writeJSONArrayStart();
603        $this->writeJSONString($this->getTypeNameForTypeID($elemType));
604        $this->writeJSONInteger($size);
605    }
606
607    public function writeListEnd()
608    {
609        $this->writeJSONArrayEnd();
610    }
611
612    public function writeSetBegin($elemType, $size)
613    {
614        $this->writeJSONArrayStart();
615        $this->writeJSONString($this->getTypeNameForTypeID($elemType));
616        $this->writeJSONInteger($size);
617    }
618
619    public function writeSetEnd()
620    {
621        $this->writeJSONArrayEnd();
622    }
623
624    public function writeBool($bool)
625    {
626        $this->writeJSONInteger($bool ? 1 : 0);
627    }
628
629    public function writeByte($byte)
630    {
631        $this->writeJSONInteger($byte);
632    }
633
634    public function writeI16($i16)
635    {
636        $this->writeJSONInteger($i16);
637    }
638
639    public function writeI32($i32)
640    {
641        $this->writeJSONInteger($i32);
642    }
643
644    public function writeI64($i64)
645    {
646        $this->writeJSONInteger($i64);
647    }
648
649    public function writeDouble($dub)
650    {
651        $this->writeJSONDouble($dub);
652    }
653
654    public function writeString($str)
655    {
656        $this->writeJSONString($str);
657    }
658
659    /**
660     * Reads the message header
661     *
662     * @param string $name Function name
663     * @param int $type message type TMessageType::CALL or TMessageType::REPLY
664     * @parem int $seqid The sequence id of this message
665     */
666    public function readMessageBegin(&$name, &$type, &$seqid)
667    {
668        $this->readJSONArrayStart();
669
670        if ($this->readJSONInteger() != self::VERSION) {
671            throw new TProtocolException("Message contained bad version", TProtocolException::BAD_VERSION);
672        }
673
674        $name = $this->readJSONString(false);
675        $type = $this->readJSONInteger();
676        $seqid = $this->readJSONInteger();
677
678        return true;
679    }
680
681    /**
682     * Read the close of message
683     */
684    public function readMessageEnd()
685    {
686        $this->readJSONArrayEnd();
687    }
688
689    public function readStructBegin(&$name)
690    {
691        $this->readJSONObjectStart();
692
693        return 0;
694    }
695
696    public function readStructEnd()
697    {
698        $this->readJSONObjectEnd();
699    }
700
701    public function readFieldBegin(&$name, &$fieldType, &$fieldId)
702    {
703        $ch = $this->reader_->peek();
704        $name = "";
705
706        if (substr($ch, 0, 1) == self::RBRACE) {
707            $fieldType = TType::STOP;
708        } else {
709            $fieldId = $this->readJSONInteger();
710            $this->readJSONObjectStart();
711            $fieldType = $this->getTypeIDForTypeName($this->readJSONString(false));
712        }
713    }
714
715    public function readFieldEnd()
716    {
717        $this->readJSONObjectEnd();
718    }
719
720    public function readMapBegin(&$keyType, &$valType, &$size)
721    {
722        $this->readJSONArrayStart();
723        $keyType = $this->getTypeIDForTypeName($this->readJSONString(false));
724        $valType = $this->getTypeIDForTypeName($this->readJSONString(false));
725        $size = $this->readJSONInteger();
726        $this->readJSONObjectStart();
727    }
728
729    public function readMapEnd()
730    {
731        $this->readJSONObjectEnd();
732        $this->readJSONArrayEnd();
733    }
734
735    public function readListBegin(&$elemType, &$size)
736    {
737        $this->readJSONArrayStart();
738        $elemType = $this->getTypeIDForTypeName($this->readJSONString(false));
739        $size = $this->readJSONInteger();
740
741        return true;
742    }
743
744    public function readListEnd()
745    {
746        $this->readJSONArrayEnd();
747    }
748
749    public function readSetBegin(&$elemType, &$size)
750    {
751        $this->readJSONArrayStart();
752        $elemType = $this->getTypeIDForTypeName($this->readJSONString(false));
753        $size = $this->readJSONInteger();
754
755        return true;
756    }
757
758    public function readSetEnd()
759    {
760        $this->readJSONArrayEnd();
761    }
762
763    public function readBool(&$bool)
764    {
765        $bool = $this->readJSONInteger() == 0 ? false : true;
766
767        return true;
768    }
769
770    public function readByte(&$byte)
771    {
772        $byte = $this->readJSONInteger();
773
774        return true;
775    }
776
777    public function readI16(&$i16)
778    {
779        $i16 = $this->readJSONInteger();
780
781        return true;
782    }
783
784    public function readI32(&$i32)
785    {
786        $i32 = $this->readJSONInteger();
787
788        return true;
789    }
790
791    public function readI64(&$i64)
792    {
793        if (PHP_INT_SIZE === 4) {
794            $i64 = $this->readJSONIntegerAsString();
795        } else {
796            $i64 = $this->readJSONInteger();
797        }
798
799        return true;
800    }
801
802    public function readDouble(&$dub)
803    {
804        $dub = $this->readJSONDouble();
805
806        return true;
807    }
808
809    public function readString(&$str)
810    {
811        $str = $this->readJSONString(false);
812
813        return true;
814    }
815}
816