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
12 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * @package thrift.protocol
24 namespace Thrift\Protocol
;
26 use Thrift\Type\TType
;
27 use Thrift\Exception\TProtocolException
;
28 use Thrift\Protocol\JSON\BaseContext
;
29 use Thrift\Protocol\JSON\LookaheadReader
;
30 use Thrift\Protocol\JSON\PairContext
;
31 use Thrift\Protocol\JSON\ListContext
;
34 * JSON implementation of thrift protocol, ported from Java.
36 class TJSONProtocol
extends TProtocol
45 const BACKSLASH
= '\\';
48 const DOUBLEESC
= '__DOUBLE_ESCAPE_SEQUENCE__';
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
59 public static $ESCAPE_CHARS = array('"', '\\', '/', "b", "f", "n", "r", "t");
61 public static $ESCAPE_CHAR_VALS = array(
62 '"', '\\', '/', "\x08", "\f", "\n", "\r", "\t",
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";
77 private function getTypeNameForTypeID($typeID)
81 return self
::NAME_BOOL
;
83 return self
::NAME_BYTE
;
85 return self
::NAME_I16
;
87 return self
::NAME_I32
;
89 return self
::NAME_I64
;
91 return self
::NAME_DOUBLE
;
93 return self
::NAME_STRING
;
95 return self
::NAME_STRUCT
;
97 return self
::NAME_MAP
;
99 return self
::NAME_SET
;
101 return self
::NAME_LIST
;
103 throw new TProtocolException("Unrecognized type", TProtocolException
::UNKNOWN
);
107 private function getTypeIDForTypeName($name)
109 $result = TType
::STOP
;
111 if (strlen($name) > 1) {
112 switch (substr($name, 0, 1)) {
114 $result = TType
::DOUBLE;
117 switch (substr($name, 1, 1)) {
119 $result = TType
::BYTE
;
122 $result = TType
::I16
;
125 $result = TType
::I32
;
128 $result = TType
::I64
;
133 $result = TType
::LST
;
136 $result = TType
::MAP
;
139 $result = TType
::STRUCT
;
142 if (substr($name, 1, 1) == 't') {
143 $result = TType
::STRING;
144 } elseif (substr($name, 1, 1) == 'e') {
145 $result = TType
::SET
;
149 $result = TType
::BOOL;
153 if ($result == TType
::STOP
) {
154 throw new TProtocolException("Unrecognized type", TProtocolException
::INVALID_DATA
);
160 public $contextStack_ = array();
164 private function pushContext($c)
166 array_push($this->contextStack_
, $this->context_
);
167 $this->context_
= $c;
170 private function popContext()
172 $this->context_
= array_pop($this->contextStack_
);
175 public function __construct($trans)
177 parent
::__construct($trans);
178 $this->context_
= new BaseContext();
179 $this->reader_
= new LookaheadReader($this);
182 public function reset()
184 $this->contextStack_
= array();
185 $this->context_
= new BaseContext();
186 $this->reader_
= new LookaheadReader($this);
189 private $tmpbuf_ = array(4);
191 public function readJSONSyntaxChar($b)
193 $ch = $this->reader_
->read();
195 if (substr($ch, 0, 1) != $b) {
196 throw new TProtocolException("Unexpected character: " . $ch, TProtocolException
::INVALID_DATA
);
200 private function hexVal($s)
202 for ($i = 0; $i < strlen($s); $i++
) {
203 $ch = substr($s, $i, 1);
205 if (!($ch >= "a" && $ch <= "f") && !($ch >= "0" && $ch <= "9")) {
206 throw new TProtocolException("Expected hex character " . $ch, TProtocolException
::INVALID_DATA
);
213 private function hexChar($val)
218 private function hasJSONUnescapedUnicode()
220 if (PHP_MAJOR_VERSION
> 5 ||
(PHP_MAJOR_VERSION
== 5 && PHP_MINOR_VERSION
>= 4)) {
227 private function unescapedUnicode($str)
229 if ($this->hasJSONUnescapedUnicode()) {
230 return json_encode($str, JSON_UNESCAPED_UNICODE
);
233 $json = json_encode($str);
236 * Unescaped character outside the Basic Multilingual Plane
237 * High surrogate: 0xD800 - 0xDBFF
238 * Low surrogate: 0xDC00 - 0xDFFF
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');
249 * Unescaped characters within the Basic Multilingual Plane
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');
262 private function writeJSONString($b)
264 $this->context_
->write();
266 if (is_numeric($b) && $this->context_
->escapeNum()) {
267 $this->trans_
->write(self
::QUOTE
);
270 $this->trans_
->write($this->unescapedUnicode($b));
272 if (is_numeric($b) && $this->context_
->escapeNum()) {
273 $this->trans_
->write(self
::QUOTE
);
277 private function writeJSONInteger($num)
279 $this->context_
->write();
281 if ($this->context_
->escapeNum()) {
282 $this->trans_
->write(self
::QUOTE
);
285 $this->trans_
->write($num);
287 if ($this->context_
->escapeNum()) {
288 $this->trans_
->write(self
::QUOTE
);
292 private function writeJSONDouble($num)
294 $this->context_
->write();
296 if ($this->context_
->escapeNum()) {
297 $this->trans_
->write(self
::QUOTE
);
300 $this->trans_
->write(json_encode($num));
302 if ($this->context_
->escapeNum()) {
303 $this->trans_
->write(self
::QUOTE
);
307 private function writeJSONBase64($data)
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
);
315 private function writeJSONObjectStart()
317 $this->context_
->write();
318 $this->trans_
->write(self
::LBRACE
);
319 $this->pushContext(new PairContext($this));
322 private function writeJSONObjectEnd()
325 $this->trans_
->write(self
::RBRACE
);
328 private function writeJSONArrayStart()
330 $this->context_
->write();
331 $this->trans_
->write(self
::LBRACKET
);
332 $this->pushContext(new ListContext($this));
335 private function writeJSONArrayEnd()
338 $this->trans_
->write(self
::RBRACKET
);
341 private function readJSONString($skipContext)
344 $this->context_
->read();
350 $ch = $this->reader_
->read();
352 if ($ch == self
::QUOTE
&&
353 $lastChar !== null &&
354 $lastChar !== self
::ESCSEQ
) {
357 if ($ch == self
::ESCSEQ
&& $lastChar == self
::ESCSEQ
) {
358 $lastChar = self
::DOUBLEESC
;
364 return json_decode($jsonString);
367 private function isJSONNumeric($b)
391 private function readJSONNumericChars()
396 $ch = $this->reader_
->peek();
398 if (!$this->isJSONNumeric($ch)) {
402 $strbld[] = $this->reader_
->read();
405 return implode("", $strbld);
408 private function readJSONInteger()
410 $this->context_
->read();
412 if ($this->context_
->escapeNum()) {
413 $this->readJSONSyntaxChar(self
::QUOTE
);
416 $str = $this->readJSONNumericChars();
418 if ($this->context_
->escapeNum()) {
419 $this->readJSONSyntaxChar(self
::QUOTE
);
422 if (!is_numeric($str)) {
423 throw new TProtocolException("Invalid data in numeric: " . $str, TProtocolException
::INVALID_DATA
);
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.
435 private function readJSONIntegerAsString()
437 $this->context_
->read();
439 if ($this->context_
->escapeNum()) {
440 $this->readJSONSyntaxChar(self
::QUOTE
);
443 $str = $this->readJSONNumericChars();
445 if ($this->context_
->escapeNum()) {
446 $this->readJSONSyntaxChar(self
::QUOTE
);
449 if (!is_numeric($str)) {
450 throw new TProtocolException("Invalid data in numeric: " . $str, TProtocolException
::INVALID_DATA
);
456 private function readJSONDouble()
458 $this->context_
->read();
460 if (substr($this->reader_
->peek(), 0, 1) == self
::QUOTE
) {
461 $arr = $this->readJSONString(true);
465 } elseif ($arr == "Infinity") {
467 } elseif (!$this->context_
->escapeNum()) {
468 throw new TProtocolException(
469 "Numeric data unexpectedly quoted " . $arr,
470 TProtocolException
::INVALID_DATA
474 return floatval($arr);
476 if ($this->context_
->escapeNum()) {
477 $this->readJSONSyntaxChar(self
::QUOTE
);
480 return floatval($this->readJSONNumericChars());
484 private function readJSONBase64()
486 $arr = $this->readJSONString(false);
487 $data = base64_decode($arr, true);
489 if ($data === false) {
490 throw new TProtocolException("Invalid base64 data " . $arr, TProtocolException
::INVALID_DATA
);
496 private function readJSONObjectStart()
498 $this->context_
->read();
499 $this->readJSONSyntaxChar(self
::LBRACE
);
500 $this->pushContext(new PairContext($this));
503 private function readJSONObjectEnd()
505 $this->readJSONSyntaxChar(self
::RBRACE
);
509 private function readJSONArrayStart()
511 $this->context_
->read();
512 $this->readJSONSyntaxChar(self
::LBRACKET
);
513 $this->pushContext(new ListContext($this));
516 private function readJSONArrayEnd()
518 $this->readJSONSyntaxChar(self
::RBRACKET
);
523 * Writes the message header
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
529 public function writeMessageBegin($name, $type, $seqid)
531 $this->writeJSONArrayStart();
532 $this->writeJSONInteger(self
::VERSION
);
533 $this->writeJSONString($name);
534 $this->writeJSONInteger($type);
535 $this->writeJSONInteger($seqid);
541 public function writeMessageEnd()
543 $this->writeJSONArrayEnd();
547 * Writes a struct header.
549 * @param string $name Struct name
550 * @throws TException on write error
551 * @return int How many bytes written
553 public function writeStructBegin($name)
555 $this->writeJSONObjectStart();
561 * @throws TException on write error
562 * @return int How many bytes written
564 public function writeStructEnd()
566 $this->writeJSONObjectEnd();
569 public function writeFieldBegin($fieldName, $fieldType, $fieldId)
571 $this->writeJSONInteger($fieldId);
572 $this->writeJSONObjectStart();
573 $this->writeJSONString($this->getTypeNameForTypeID($fieldType));
576 public function writeFieldEnd()
578 $this->writeJsonObjectEnd();
581 public function writeFieldStop()
585 public function writeMapBegin($keyType, $valType, $size)
587 $this->writeJSONArrayStart();
588 $this->writeJSONString($this->getTypeNameForTypeID($keyType));
589 $this->writeJSONString($this->getTypeNameForTypeID($valType));
590 $this->writeJSONInteger($size);
591 $this->writeJSONObjectStart();
594 public function writeMapEnd()
596 $this->writeJSONObjectEnd();
597 $this->writeJSONArrayEnd();
600 public function writeListBegin($elemType, $size)
602 $this->writeJSONArrayStart();
603 $this->writeJSONString($this->getTypeNameForTypeID($elemType));
604 $this->writeJSONInteger($size);
607 public function writeListEnd()
609 $this->writeJSONArrayEnd();
612 public function writeSetBegin($elemType, $size)
614 $this->writeJSONArrayStart();
615 $this->writeJSONString($this->getTypeNameForTypeID($elemType));
616 $this->writeJSONInteger($size);
619 public function writeSetEnd()
621 $this->writeJSONArrayEnd();
624 public function writeBool($bool)
626 $this->writeJSONInteger($bool ?
1 : 0);
629 public function writeByte($byte)
631 $this->writeJSONInteger($byte);
634 public function writeI16($i16)
636 $this->writeJSONInteger($i16);
639 public function writeI32($i32)
641 $this->writeJSONInteger($i32);
644 public function writeI64($i64)
646 $this->writeJSONInteger($i64);
649 public function writeDouble($dub)
651 $this->writeJSONDouble($dub);
654 public function writeString($str)
656 $this->writeJSONString($str);
660 * Reads the message header
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
666 public function readMessageBegin(&$name, &$type, &$seqid)
668 $this->readJSONArrayStart();
670 if ($this->readJSONInteger() != self
::VERSION
) {
671 throw new TProtocolException("Message contained bad version", TProtocolException
::BAD_VERSION
);
674 $name = $this->readJSONString(false);
675 $type = $this->readJSONInteger();
676 $seqid = $this->readJSONInteger();
682 * Read the close of message
684 public function readMessageEnd()
686 $this->readJSONArrayEnd();
689 public function readStructBegin(&$name)
691 $this->readJSONObjectStart();
696 public function readStructEnd()
698 $this->readJSONObjectEnd();
701 public function readFieldBegin(&$name, &$fieldType, &$fieldId)
703 $ch = $this->reader_
->peek();
706 if (substr($ch, 0, 1) == self
::RBRACE
) {
707 $fieldType = TType
::STOP
;
709 $fieldId = $this->readJSONInteger();
710 $this->readJSONObjectStart();
711 $fieldType = $this->getTypeIDForTypeName($this->readJSONString(false));
715 public function readFieldEnd()
717 $this->readJSONObjectEnd();
720 public function readMapBegin(&$keyType, &$valType, &$size)
722 $this->readJSONArrayStart();
723 $keyType = $this->getTypeIDForTypeName($this->readJSONString(false));
724 $valType = $this->getTypeIDForTypeName($this->readJSONString(false));
725 $size = $this->readJSONInteger();
726 $this->readJSONObjectStart();
729 public function readMapEnd()
731 $this->readJSONObjectEnd();
732 $this->readJSONArrayEnd();
735 public function readListBegin(&$elemType, &$size)
737 $this->readJSONArrayStart();
738 $elemType = $this->getTypeIDForTypeName($this->readJSONString(false));
739 $size = $this->readJSONInteger();
744 public function readListEnd()
746 $this->readJSONArrayEnd();
749 public function readSetBegin(&$elemType, &$size)
751 $this->readJSONArrayStart();
752 $elemType = $this->getTypeIDForTypeName($this->readJSONString(false));
753 $size = $this->readJSONInteger();
758 public function readSetEnd()
760 $this->readJSONArrayEnd();
763 public function readBool(&$bool)
765 $bool = $this->readJSONInteger() == 0 ?
false : true;
770 public function readByte(&$byte)
772 $byte = $this->readJSONInteger();
777 public function readI16(&$i16)
779 $i16 = $this->readJSONInteger();
784 public function readI32(&$i32)
786 $i32 = $this->readJSONInteger();
791 public function readI64(&$i64)
793 if (PHP_INT_SIZE
=== 4) {
794 $i64 = $this->readJSONIntegerAsString();
796 $i64 = $this->readJSONInteger();
802 public function readDouble(&$dub)
804 $dub = $this->readJSONDouble();
809 public function readString(&$str)
811 $str = $this->readJSONString(false);