$contexts; public function __construct(TTransport $trans) { $this->contexts = Vector {}; if (!($trans instanceof IThriftBufferedTransport)) { $trans = new TBufferedTransport($trans); } $this->bufTrans = $trans; parent::__construct($trans); $this->contexts ->add(new TSimpleJSONProtocolContext($this->trans_, $this->bufTrans)); } private function pushListWriteContext(): int { return $this->pushWriteContext( new TSimpleJSONProtocolListContext($this->trans_, $this->bufTrans), ); } private function pushMapWriteContext(): int { return $this->pushWriteContext( new TSimpleJSONProtocolMapContext($this->trans_, $this->bufTrans), ); } private function pushListReadContext(): void { $this->pushReadContext( new TSimpleJSONProtocolListContext($this->trans_, $this->bufTrans), ); } private function pushMapReadContext(): void { $this->pushReadContext( new TSimpleJSONProtocolMapContext($this->trans_, $this->bufTrans), ); } private function pushWriteContext(TSimpleJSONProtocolContext $ctx): int { $this->contexts->add($ctx); return $ctx->writeStart(); } private function popWriteContext(): int { $ctx = $this->contexts->pop(); return $ctx->writeEnd(); } private function pushReadContext(TSimpleJSONProtocolContext $ctx): void { $this->contexts->add($ctx); $ctx->readStart(); } private function popReadContext(): void { $ctx = $this->contexts->pop(); $ctx->readEnd(); } private function getContext(): TSimpleJSONProtocolContext { return $this->contexts->at($this->contexts->count() - 1); } public function writeMessageBegin($name, $type, $seqid) { return $this->getContext()->writeSeparator() + $this->pushListWriteContext() + $this->writeI32(self::VERSION_1) + $this->writeString($name) + $this->writeI32($type) + $this->writeI32($seqid); } public function writeMessageEnd() { return $this->popWriteContext(); } public function writeStructBegin($name) { return $this->getContext()->writeSeparator() + $this->pushMapWriteContext(); } public function writeStructEnd() { return $this->popWriteContext(); } public function writeFieldBegin($fieldName, $fieldType, $fieldId) { return $this->writeString($fieldName); } public function writeFieldEnd() { return 0; } public function writeFieldStop() { return 0; } public function writeMapBegin($keyType, $valType, $size) { return $this->getContext()->writeSeparator() + $this->pushMapWriteContext(); } public function writeMapEnd() { return $this->popWriteContext(); } public function writeListBegin($elemType, $size) { return $this->getContext()->writeSeparator() + $this->pushListWriteContext(); } public function writeListEnd() { return $this->popWriteContext(); } public function writeSetBegin($elemType, $size) { return $this->getContext()->writeSeparator() + $this->pushListWriteContext(); } public function writeSetEnd() { return $this->popWriteContext(); } public function writeBool($value) { $x = $this->getContext()->writeSeparator(); if ($value) { $this->trans_->write('true'); $x += 4; } else { $this->trans_->write('false'); $x += 5; } return $x; } public function writeByte($value) { return $this->writeNum((int) $value); } public function writeI16($value) { return $this->writeNum((int) $value); } public function writeI32($value) { return $this->writeNum((int) $value); } public function writeI64($value) { return $this->writeNum((int) $value); } public function writeDouble($value) { return $this->writeNum((float) $value); } public function writeFloat($value) { return $this->writeNum((float) $value); } private function writeNum($value) { $ctx = $this->getContext(); $ret = $ctx->writeSeparator(); if ($ctx->escapeNum()) { $value = (string) $value; } $enc = json_encode($value); $this->trans_->write($enc); return $ret + strlen($enc); } public function writeString($value) { $ctx = $this->getContext(); $ret = $ctx->writeSeparator(); $value = (string) $value; $sb = ''; $sb .= '"'; $len = strlen($value); for ($i = 0; $i < $len; $i++) { $c = $value[$i]; $ord = ord($c); switch ($ord) { case 8: $sb .= '\b'; break; case 9: $sb .= '\t'; break; case 10: $sb .= '\n'; break; case 12: $sb .= '\f'; break; case 13: $sb .= '\r'; break; case 34: // " case 92: // \ $sb .= '\\'; $sb .= $c; break; default: if ($ord < 32 || $ord > 126) { $sb .= '\\u00'; $sb .= bin2hex($c); } else { $sb .= $c; } break; } } $sb .= '"'; $enc = $sb; $this->trans_->write($enc); return $ret + strlen($enc); } public function readMessageBegin(inout $name, inout $type, inout $seqid) { throw new TProtocolException( 'Reading with TSimpleJSONProtocol is not supported. '. 'Use readFromJSON() on your struct', ); } public function readMessageEnd() { throw new TProtocolException( 'Reading with TSimpleJSONProtocol is not supported. '. 'Use readFromJSON() on your struct', ); } public function readStructBegin(inout $_name) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $this->pushMapReadContext(); } public function readStructEnd() { $this->popReadContext(); } public function readFieldBegin( inout $name, inout $fieldType, inout $fieldId, ) { $fieldId = null; $ctx = $this->getContext(); $name = null; while ($name === null) { if ($ctx->readContextOver()) { $fieldType = TType::STOP; break; } else { $ctx->readSeparator(); $this->skipWhitespace(); $name = $this->readJSONString()[0]; // We need to guess the type of the value, in case the name is bogus or we are in a skip method up the stack $offset = $this->skipWhitespace(true); $this->expectChar(':', true, $offset); $offset += 1 + $this->skipWhitespace(true, $offset + 1); $c = $this->bufTrans->peek(1, $offset); if ($c === 'n' && $this->bufTrans->peek(4, $offset) === 'null') { // We actually want to skip this field, but there isn't an appropriate // TType to send back. So instead, we will silently skip $ctx->readSeparator(); $this->skipWhitespace(); $this->trans_->readAll(4); $name = null; continue; } else { $fieldType = $this->guessFieldTypeBasedOnByte($c); } } } } public function readFieldEnd() { // Do nothing } public function readMapBegin(inout $keyType, inout $valType, inout $size) { $size = null; $this->getContext()->readSeparator(); $this->skipWhitespace(); $this->pushMapReadContext(); if ($this->readMapHasNext()) { // We need to guess the type of the keys/values, in case we are in a skip method up the stack $keyType = TType::STRING; $this->skipWhitespace(); // This is not a peek, since we can do this safely again $offset = $this->readJSONString(true)[1]; $offset += $this->skipWhitespace(true, $offset); $this->expectChar(':', true, $offset); $offset += 1 + $this->skipWhitespace(true, $offset + 1); $c = $this->bufTrans->peek(1, $offset); $valType = $this->guessFieldTypeBasedOnByte($c); } } public function readMapHasNext(): bool { return !$this->getContext()->readContextOver(); } public function readMapEnd() { $this->popReadContext(); } public function readListBegin(&$elemType, &$size) { $size = null; $this->getContext()->readSeparator(); $this->skipWhitespace(); $this->pushListReadContext(); if ($this->readListHasNext()) { // We need to guess the type of the values, in case we are in a skip method up the stack $this->skipWhitespace(); // This is not a peek, since we can do this safely again $c = $this->bufTrans->peek(1); $elemType = $this->guessFieldTypeBasedOnByte($c); } } public function readListHasNext(): bool { return !$this->getContext()->readContextOver(); } public function readListEnd() { $this->popReadContext(); } public function readSetBegin(&$elemType, &$size) { $size = null; $this->getContext()->readSeparator(); $this->skipWhitespace(); $this->pushListReadContext(); if ($this->readSetHasNext()) { // We need to guess the type of the values, in case we are in a skip method up the stack $this->skipWhitespace(); // This is not a peek, since we can do this safely again $c = $this->bufTrans->peek(1); $elemType = $this->guessFieldTypeBasedOnByte($c); } } public function readSetHasNext(): bool { return !$this->getContext()->readContextOver(); } public function readSetEnd() { $this->popReadContext(); } public function readBool(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $c = $this->trans_->readAll(1); $target = null; switch ($c) { case 't': $value = true; $target = 'rue'; break; case 'f': $value = false; $target = 'alse'; break; default: throw new TProtocolException( 'TSimpleJSONProtocol: Expected t or f, encountered 0x'. bin2hex($c), ); } for ($i = 0; $i < strlen($target); $i++) { $this->expectChar($target[$i]); } } private function readInteger(?int $min, ?int $max): int { $val = intval($this->readNumStr()); if (($min !== null && $max !== null) && ($val < $min || $val > $max)) { throw new TProtocolException( 'TProtocolException: value '. $val. ' is outside the expected bounds', ); } return $val; } private function readNumStr(): string { $ctx = $this->getContext(); if ($ctx->escapeNum()) { $this->expectChar('"'); } $count = 0; $reading = true; while ($reading) { $c = $this->bufTrans->peek(1, $count); switch ($c) { case '+': case '-': case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'E': case 'e': $count++; break; default: $reading = false; break; } } $str = $this->trans_->readAll($count); if (!preg_match( '/^[+-]?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/', $str, )) { throw new TProtocolException( 'TSimpleJSONProtocol: Invalid json number '.$str, ); } if ($ctx->escapeNum()) { $this->expectChar('"'); } return $str; } public function readByte(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = $this->readInteger(-0x80, 0x7f); } public function readI16(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = $this->readInteger(-0x8000, 0x7fff); } public function readI32(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = $this->readInteger(-0x80000000, 0x7fffffff); } public function readI64(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = $this->readInteger(null, null); } public function readDouble(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = (float)$this->readNumStr(); } public function readFloat(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = (float)$this->readNumStr(); } public function readString(&$value) { $this->getContext()->readSeparator(); $this->skipWhitespace(); $value = $this->readJSONString()[0]; } private function readJSONString( bool $peek = false, int $start = 0, ): Pair { if (!$peek) { $start = 0; } $this->expectChar('"', $peek, $start); $count = $peek ? 1 : 0; $sb = ''; $reading = true; while ($reading) { $c = $this->bufTrans->peek(1, $start + $count); switch ($c) { case '"': $reading = false; break; case '\\': $count++; $c = $this->bufTrans->peek(1, $start + $count); switch ($c) { case '\\': $count++; $sb .= '\\'; break; case '"': $count++; $sb .= '"'; break; case 'b': $count++; $sb .= chr(0x08); break; case '/': $count++; $sb .= '/'; break; case 'f': $count++; $sb .= "\f"; break; case 'n': $count++; $sb .= "\n"; break; case 'r': $count++; $sb .= "\r"; break; case 't': $count++; $sb .= "\t"; break; case 'u': $count++; $this->expectChar('0', true, $start + $count); $this->expectChar('0', true, $start + $count + 1); $count += 2; $sb .= hex2bin($this->bufTrans->peek(2, $start + $count)); $count += 2; break; default: throw new TProtocolException( 'TSimpleJSONProtocol: Expected Control Character, found 0x'. bin2hex($c), ); } break; case '': // end of buffer, this string is unclosed $reading = false; break; default: $count++; $sb .= $c; break; } } if (!$peek) { $this->trans_->readAll($count); } $this->expectChar('"', $peek, $start + $count); return Pair {$sb, $count + 1}; } private function skipWhitespace(bool $peek = false, int $start = 0): int { if (!$peek) { $start = 0; } $count = 0; $reading = true; while ($reading) { $byte = $this->bufTrans->peek(1, $count + $start); switch ($byte) { case ' ': case "\t": case "\n": case "\r": $count++; break; default: $reading = false; break; } } if (!$peek) { $this->trans_->readAll($count); } return $count; } private function expectChar( string $char, bool $peek = false, int $start = 0, ): void { if (!$peek) { $start = 0; } $c = null; if ($peek) { $c = $this->bufTrans->peek(1, $start); } else { $c = $this->trans_->readAll(1); } if ($c !== $char) { throw new TProtocolException( 'TSimpleJSONProtocol: Expected '. $char. ', but encountered 0x'. bin2hex($c), ); } } private function guessFieldTypeBasedOnByte(string $byte): ?int { switch ($byte) { case '{': return TType::STRUCT; case '[': return TType::LST; case 't': case 'f': return TType::BOOL; case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // These technically aren't allowed to start JSON floats, but are here for backwards compatibility case '+': case '.': return TType::DOUBLE; case '"': return TType::STRING; case ']': case '}': // We can get here with empty lists/maps, returning a dummy value return TType::STOP; } throw new TProtocolException( 'TSimpleJSONProtocol: Unable to guess TType for character 0x'. bin2hex($byte), ); } }