blob: 402401ac6063e42973dcd2905decbf3ab2e27a1b [file] [log] [blame]
Roger Meier21c0a852012-09-05 19:47:14 +00001<?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\Protocol\TProtocol;
27use Thrift\Type\TType;
28use Thrift\Exception\TProtocolException;
29use Thrift\Protocol\JSON\BaseContext;
30use Thrift\Protocol\JSON\LookaheadReader;
31use Thrift\Protocol\JSON\PairContext;
32use Thrift\Protocol\JSON\ListContext;
33
34/**
35 * JSON implementation of thrift protocol, ported from Java.
36 */
37class TJSONProtocol extends TProtocol
38{
39 const COMMA = ',';
40 const COLON = ':';
41 const LBRACE = '{';
42 const RBRACE = '}';
43 const LBRACKET = '[';
44 const RBRACKET = ']';
45 const QUOTE = '"';
46 const BACKSLASH = '\\';
47 const ZERO = '0';
48 const ESCSEQ = '\\';
49 const DOUBLEESC = '__DOUBLE_ESCAPE_SEQUENCE__';
50
51 const VERSION = 1;
52
53 public static $JSON_CHAR_TABLE = array(
54 /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
55 0, 0, 0, 0, 0, 0, 0, 0, 'b', 't', 'n', 0, 'f', 'r', 0, 0, // 0
56 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
57 1, 1, '"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
58 );
59
Jens Geyer79759502013-12-26 18:56:54 +010060 public static $ESCAPE_CHARS = array('"', '\\', '/', "b", "f", "n", "r", "t");
Roger Meier21c0a852012-09-05 19:47:14 +000061
62 public static $ESCAPE_CHAR_VALS = array(
Jens Geyer79759502013-12-26 18:56:54 +010063 '"', '\\', '/', "\x08", "\f", "\n", "\r", "\t",
Roger Meier21c0a852012-09-05 19:47:14 +000064 );
65
66 const NAME_BOOL = "tf";
67 const NAME_BYTE = "i8";
68 const NAME_I16 = "i16";
69 const NAME_I32 = "i32";
70 const NAME_I64 = "i64";
71 const NAME_DOUBLE = "dbl";
72 const NAME_STRUCT = "rec";
73 const NAME_STRING = "str";
74 const NAME_MAP = "map";
75 const NAME_LIST = "lst";
76 const NAME_SET = "set";
77
78 private function getTypeNameForTypeID($typeID)
79 {
80 switch ($typeID) {
81 case TType::BOOL:
82 return self::NAME_BOOL;
83 case TType::BYTE:
84 return self::NAME_BYTE;
85 case TType::I16:
86 return self::NAME_I16;
87 case TType::I32:
88 return self::NAME_I32;
89 case TType::I64:
90 return self::NAME_I64;
91 case TType::DOUBLE:
92 return self::NAME_DOUBLE;
93 case TType::STRING:
94 return self::NAME_STRING;
95 case TType::STRUCT:
96 return self::NAME_STRUCT;
97 case TType::MAP:
98 return self::NAME_MAP;
99 case TType::SET:
100 return self::NAME_SET;
101 case TType::LST:
102 return self::NAME_LIST;
103 default:
104 throw new TProtocolException("Unrecognized type", TProtocolException::UNKNOWN);
105 }
106 }
107
108 private function getTypeIDForTypeName($name)
109 {
110 $result = TType::STOP;
111
112 if (strlen($name) > 1) {
113 switch (substr($name, 0, 1)) {
114 case 'd':
115 $result = TType::DOUBLE;
116 break;
117 case 'i':
118 switch (substr($name, 1, 1)) {
119 case '8':
120 $result = TType::BYTE;
121 break;
122 case '1':
123 $result = TType::I16;
124 break;
125 case '3':
126 $result = TType::I32;
127 break;
128 case '6':
129 $result = TType::I64;
130 break;
131 }
132 break;
133 case 'l':
134 $result = TType::LST;
135 break;
136 case 'm':
137 $result = TType::MAP;
138 break;
139 case 'r':
140 $result = TType::STRUCT;
141 break;
142 case 's':
143 if (substr($name, 1, 1) == 't') {
144 $result = TType::STRING;
145 }
146 else if (substr($name, 1, 1) == 'e') {
147 $result = TType::SET;
148 }
149 break;
150 case 't':
151 $result = TType::BOOL;
152 break;
153 }
154 }
155 if ($result == TType::STOP) {
156 throw new TProtocolException("Unrecognized type", TProtocolException::INVALID_DATA);
157 }
158 return $result;
159 }
160
161 public $contextStack_ = array();
162 public $context_;
163 public $reader_;
164
165 private function pushContext($c) {
166 array_push($this->contextStack_, $this->context_);
167 $this->context_ = $c;
168 }
169
170 private function popContext() {
171 $this->context_ = array_pop($this->contextStack_);
172 }
173
174 public function __construct($trans) {
175 parent::__construct($trans);
176 $this->context_ = new BaseContext();
177 $this->reader_ = new LookaheadReader($this);
178 }
179
180 public function reset() {
181 $this->contextStack_ = array();
182 $this->context_ = new BaseContext();
183 $this->reader_ = new LookaheadReader($this);
184 }
185
186 private $tmpbuf_ = array(4);
187
188 public function readJSONSyntaxChar($b) {
189 $ch = $this->reader_->read();
190
191 if (substr($ch, 0, 1) != $b) {
192 throw new TProtocolException("Unexpected character: " . $ch, TProtocolException::INVALID_DATA);
193 }
194 }
195
196 private function hexVal($s) {
197 for ($i = 0; $i < strlen($s); $i++) {
198 $ch = substr($s, $i, 1);
199
200 if (!($ch >= "a" && $ch <= "f") && !($ch >= "0" && $ch <= "9")) {
201 throw new TProtocolException("Expected hex character " . $ch, TProtocolException::INVALID_DATA);
202 }
203 }
204
205 return hexdec($s);
206 }
207
208 private function hexChar($val) {
209 return dechex($val);
210 }
211
212 private function writeJSONString($b) {
213 $this->context_->write();
214
215 if (is_numeric($b) && $this->context_->escapeNum()) {
216 $this->trans_->write(self::QUOTE);
217 }
218
219 $this->trans_->write(json_encode($b));
220
221 if (is_numeric($b) && $this->context_->escapeNum()) {
222 $this->trans_->write(self::QUOTE);
223 }
224 }
225
226 private function writeJSONInteger($num) {
227 $this->context_->write();
228
229 if ($this->context_->escapeNum()) {
230 $this->trans_->write(self::QUOTE);
231 }
232
233 $this->trans_->write($num);
234
235 if ($this->context_->escapeNum()) {
236 $this->trans_->write(self::QUOTE);
237 }
238 }
239
240 private function writeJSONDouble($num) {
241 $this->context_->write();
242
243 if ($this->context_->escapeNum()) {
244 $this->trans_->write(self::QUOTE);
245 }
246
247 $this->trans_->write(json_encode($num));
248
249 if ($this->context_->escapeNum()) {
250 $this->trans_->write(self::QUOTE);
251 }
252 }
253
254 private function writeJSONBase64($data) {
255 $this->context_->write();
256 $this->trans_->write(self::QUOTE);
257 $this->trans_->write(json_encode(base64_encode($data)));
258 $this->trans_->write(self::QUOTE);
259 }
260
261 private function writeJSONObjectStart() {
262 $this->context_->write();
263 $this->trans_->write(self::LBRACE);
264 $this->pushContext(new PairContext($this));
265 }
266
267 private function writeJSONObjectEnd() {
268 $this->popContext();
269 $this->trans_->write(self::RBRACE);
270 }
271
272 private function writeJSONArrayStart() {
273 $this->context_->write();
274 $this->trans_->write(self::LBRACKET);
275 $this->pushContext(new ListContext($this));
276 }
277
278 private function writeJSONArrayEnd() {
279 $this->popContext();
280 $this->trans_->write(self::RBRACKET);
281 }
282
283 private function readJSONString($skipContext) {
284 if (!$skipContext) {
285 $this->context_->read();
286 }
287
288 $jsonString = '';
289 $lastChar = NULL;
290 while (true) {
291 $ch = $this->reader_->read();
292 $jsonString .= $ch;
293 if ($ch == self::QUOTE &&
294 $lastChar !== NULL &&
295 $lastChar !== self::ESCSEQ) {
296 break;
297 }
298 if ($ch == self::ESCSEQ && $lastChar == self::ESCSEQ) {
299 $lastChar = self::DOUBLEESC;
300 } else {
301 $lastChar = $ch;
302 }
303 }
304 return json_decode($jsonString);
305 }
306
307 private function isJSONNumeric($b) {
308 switch ($b) {
309 case '+':
310 case '-':
311 case '.':
312 case '0':
313 case '1':
314 case '2':
315 case '3':
316 case '4':
317 case '5':
318 case '6':
319 case '7':
320 case '8':
321 case '9':
322 case 'E':
323 case 'e':
324 return true;
325 }
326 return false;
327 }
328
329 private function readJSONNumericChars() {
330 $strbld = array();
331
332 while (true) {
333 $ch = $this->reader_->peek();
334
335 if (!$this->isJSONNumeric($ch)) {
336 break;
337 }
338
339 $strbld[] = $this->reader_->read();
340 }
341
342 return implode("", $strbld);
343 }
344
345 private function readJSONInteger() {
346 $this->context_->read();
347
348 if ($this->context_->escapeNum()) {
349 $this->readJSONSyntaxChar(self::QUOTE);
350 }
351
352 $str = $this->readJSONNumericChars();
353
354 if ($this->context_->escapeNum()) {
355 $this->readJSONSyntaxChar(self::QUOTE);
356 }
357
358 if (!is_numeric($str)) {
359 throw new TProtocolException("Invalid data in numeric: " . $str, TProtocolException::INVALID_DATA);
360 }
361
362 return intval($str);
363 }
364
365 /**
366 * Identical to readJSONInteger but without the final cast.
367 * Needed for proper handling of i64 on 32 bit machines. Why a
368 * separate function? So we don't have to force the rest of the
369 * use cases through the extra conditional.
370 */
371 private function readJSONIntegerAsString() {
372 $this->context_->read();
373
374 if ($this->context_->escapeNum()) {
375 $this->readJSONSyntaxChar(self::QUOTE);
376 }
377
378 $str = $this->readJSONNumericChars();
379
380 if ($this->context_->escapeNum()) {
381 $this->readJSONSyntaxChar(self::QUOTE);
382 }
383
384 if (!is_numeric($str)) {
385 throw new TProtocolException("Invalid data in numeric: " . $str, TProtocolException::INVALID_DATA);
386 }
387
388 return $str;
389 }
390
391 private function readJSONDouble() {
392 $this->context_->read();
393
394 if (substr($this->reader_->peek(), 0, 1) == self::QUOTE) {
395 $arr = $this->readJSONString(true);
396
397 if ($arr == "NaN") {
398 return NAN;
399 } else if ($arr == "Infinity") {
400 return INF;
401 } else if (!$this->context_->escapeNum()) {
402 throw new TProtocolException("Numeric data unexpectedly quoted " . $arr,
403 TProtocolException::INVALID_DATA);
404 }
405
406 return floatval($arr);
407 } else {
408 if ($this->context_->escapeNum()) {
409 $this->readJSONSyntaxChar(self::QUOTE);
410 }
411
412 return floatval($this->readJSONNumericChars());
413 }
414 }
415
416 private function readJSONBase64() {
417 $arr = $this->readJSONString(false);
418 $data = base64_decode($arr, true);
419
420 if ($data === false) {
421 throw new TProtocolException("Invalid base64 data " . $arr, TProtocolException::INVALID_DATA);
422 }
423
424 return $data;
425 }
426
427 private function readJSONObjectStart() {
428 $this->context_->read();
429 $this->readJSONSyntaxChar(self::LBRACE);
430 $this->pushContext(new PairContext($this));
431 }
432
433 private function readJSONObjectEnd() {
434 $this->readJSONSyntaxChar(self::RBRACE);
435 $this->popContext();
436 }
437
438 private function readJSONArrayStart()
439 {
440 $this->context_->read();
441 $this->readJSONSyntaxChar(self::LBRACKET);
442 $this->pushContext(new ListContext($this));
443 }
444
445 private function readJSONArrayEnd() {
446 $this->readJSONSyntaxChar(self::RBRACKET);
447 $this->popContext();
448 }
449
450 /**
451 * Writes the message header
452 *
453 * @param string $name Function name
454 * @param int $type message type TMessageType::CALL or TMessageType::REPLY
455 * @param int $seqid The sequence id of this message
456 */
457 public function writeMessageBegin($name, $type, $seqid) {
458 $this->writeJSONArrayStart();
459 $this->writeJSONInteger(self::VERSION);
460 $this->writeJSONString($name);
461 $this->writeJSONInteger($type);
462 $this->writeJSONInteger($seqid);
463 }
464
465 /**
466 * Close the message
467 */
468 public function writeMessageEnd() {
469 $this->writeJSONArrayEnd();
470 }
471
472 /**
473 * Writes a struct header.
474 *
475 * @param string $name Struct name
476 * @throws TException on write error
477 * @return int How many bytes written
478 */
479 public function writeStructBegin($name) {
480 $this->writeJSONObjectStart();
481 }
482
483 /**
484 * Close a struct.
485 *
486 * @throws TException on write error
487 * @return int How many bytes written
488 */
489 public function writeStructEnd() {
490 $this->writeJSONObjectEnd();
491 }
492
493 public function writeFieldBegin($fieldName, $fieldType, $fieldId) {
494 $this->writeJSONInteger($fieldId);
495 $this->writeJSONObjectStart();
496 $this->writeJSONString($this->getTypeNameForTypeID($fieldType));
497 }
498
499 public function writeFieldEnd() {
500 $this->writeJsonObjectEnd();
501 }
502
503 public function writeFieldStop() {
504 }
505
506 public function writeMapBegin($keyType, $valType, $size) {
507 $this->writeJSONArrayStart();
508 $this->writeJSONString($this->getTypeNameForTypeID($keyType));
509 $this->writeJSONString($this->getTypeNameForTypeID($valType));
510 $this->writeJSONInteger($size);
511 $this->writeJSONObjectStart();
512 }
513
514 public function writeMapEnd() {
515 $this->writeJSONObjectEnd();
516 $this->writeJSONArrayEnd();
517 }
518
519 public function writeListBegin($elemType, $size) {
520 $this->writeJSONArrayStart();
521 $this->writeJSONString($this->getTypeNameForTypeID($elemType));
522 $this->writeJSONInteger($size);
523 }
524
525 public function writeListEnd() {
526 $this->writeJSONArrayEnd();
527 }
528
529 public function writeSetBegin($elemType, $size) {
530 $this->writeJSONArrayStart();
531 $this->writeJSONString($this->getTypeNameForTypeID($elemType));
532 $this->writeJSONInteger($size);
533 }
534
535 public function writeSetEnd() {
536 $this->writeJSONArrayEnd();
537 }
538
539 public function writeBool($bool) {
540 $this->writeJSONInteger($bool ? 1 : 0);
541 }
542
543 public function writeByte($byte) {
544 $this->writeJSONInteger($byte);
545 }
546
547 public function writeI16($i16) {
548 $this->writeJSONInteger($i16);
549 }
550
551 public function writeI32($i32) {
552 $this->writeJSONInteger($i32);
553 }
554
555 public function writeI64($i64) {
556 $this->writeJSONInteger($i64);
557 }
558
559 public function writeDouble($dub) {
560 $this->writeJSONDouble($dub);
561 }
562
563 public function writeString($str) {
564 $this->writeJSONString($str);
565 }
566
567 /**
568 * Reads the message header
569 *
570 * @param string $name Function name
571 * @param int $type message type TMessageType::CALL or TMessageType::REPLY
572 * @parem int $seqid The sequence id of this message
573 */
574 public function readMessageBegin(&$name, &$type, &$seqid) {
575 $this->readJSONArrayStart();
576
577 if ($this->readJSONInteger() != self::VERSION) {
578 throw new TProtocolException("Message contained bad version", TProtocolException::BAD_VERSION);
579 }
580
581 $name = $this->readJSONString(false);
582 $type = $this->readJSONInteger();
583 $seqid = $this->readJSONInteger();
584
585 return true;
586 }
587
588 /**
589 * Read the close of message
590 */
591 public function readMessageEnd() {
592 $this->readJSONArrayEnd();
593 }
594
595 public function readStructBegin(&$name) {
596 $this->readJSONObjectStart();
597 return 0;
598 }
599
600 public function readStructEnd() {
601 $this->readJSONObjectEnd();
602 }
603
604 public function readFieldBegin(&$name, &$fieldType, &$fieldId) {
605 $ch = $this->reader_->peek();
606 $name = "";
607
608 if (substr($ch, 0, 1) == self::RBRACE) {
609 $fieldType = TType::STOP;
610 } else {
611 $fieldId = $this->readJSONInteger();
612 $this->readJSONObjectStart();
613 $fieldType = $this->getTypeIDForTypeName($this->readJSONString(false));
614 }
615 }
616
617 public function readFieldEnd() {
618 $this->readJSONObjectEnd();
619 }
620
621 public function readMapBegin(&$keyType, &$valType, &$size) {
622 $this->readJSONArrayStart();
623 $keyType = $this->getTypeIDForTypeName($this->readJSONString(false));
624 $valType = $this->getTypeIDForTypeName($this->readJSONString(false));
625 $size = $this->readJSONInteger();
626 $this->readJSONObjectStart();
627 }
628
629 public function readMapEnd() {
630 $this->readJSONObjectEnd();
631 $this->readJSONArrayEnd();
632 }
633
634 public function readListBegin(&$elemType, &$size) {
635 $this->readJSONArrayStart();
636 $elemType = $this->getTypeIDForTypeName($this->readJSONString(false));
637 $size = $this->readJSONInteger();
638 return true;
639 }
640
641 public function readListEnd() {
642 $this->readJSONArrayEnd();
643 }
644
645 public function readSetBegin(&$elemType, &$size) {
646 $this->readJSONArrayStart();
647 $elemType = $this->getTypeIDForTypeName($this->readJSONString(false));
648 $size = $this->readJSONInteger();
649 return true;
650 }
651
652 public function readSetEnd() {
653 $this->readJSONArrayEnd();
654 }
655
656 public function readBool(&$bool) {
657 $bool = $this->readJSONInteger() == 0 ? false : true;
658 return true;
659 }
660
661 public function readByte(&$byte) {
662 $byte = $this->readJSONInteger();
663 return true;
664 }
665
666 public function readI16(&$i16) {
667 $i16 = $this->readJSONInteger();
668 return true;
669 }
670
671 public function readI32(&$i32) {
672 $i32 = $this->readJSONInteger();
673 return true;
674 }
675
676 public function readI64(&$i64) {
677 if ( PHP_INT_SIZE === 4 ) {
678 $i64 = $this->readJSONIntegerAsString();
679 } else {
680 $i64 = $this->readJSONInteger();
681 }
682 return true;
683 }
684
685 public function readDouble(&$dub) {
686 $dub = $this->readJSONDouble();
687 return true;
688 }
689
690 public function readString(&$str) {
691 $str = $this->readJSONString(false);
692 return true;
693 }
694}