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