2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
24 #include "zend_interfaces.h"
25 #include "zend_exceptions.h"
26 #include "php_thrift_protocol.h"
28 #if PHP_VERSION_ID >= 70000
30 #include <sys/types.h>
31 #include <arpa/inet.h>
38 #define bswap_64(x) (((uint64_t)(x) << 56) | \
39 (((uint64_t)(x) << 40) & 0xff000000000000ULL) | \
40 (((uint64_t)(x) << 24) & 0xff0000000000ULL) | \
41 (((uint64_t)(x) << 8) & 0xff00000000ULL) | \
42 (((uint64_t)(x) >> 8) & 0xff000000ULL) | \
43 (((uint64_t)(x) >> 24) & 0xff0000ULL) | \
44 (((uint64_t)(x) >> 40) & 0xff00ULL) | \
45 ((uint64_t)(x) >> 56))
48 #if __BYTE_ORDER == __LITTLE_ENDIAN
49 #define htonll(x) bswap_64(x)
50 #define ntohll(x) bswap_64(x)
51 #elif __BYTE_ORDER == __BIG_ENDIAN
55 #error Unknown __BYTE_ORDER
79 const int32_t VERSION_MASK
= 0xffff0000;
80 const int32_t VERSION_1
= 0x80010000;
81 const int8_t T_CALL
= 1;
82 const int8_t T_REPLY
= 2;
83 const int8_t T_EXCEPTION
= 3;
85 const int INVALID_DATA
= 1;
86 const int BAD_VERSION
= 4;
88 static zend_function_entry thrift_protocol_functions
[] = {
89 PHP_FE(thrift_protocol_write_binary
, nullptr)
90 PHP_FE(thrift_protocol_read_binary
, nullptr)
91 PHP_FE(thrift_protocol_read_binary_after_message_begin
, nullptr)
92 {nullptr, nullptr, nullptr}
95 zend_module_entry thrift_protocol_module_entry
= {
96 STANDARD_MODULE_HEADER
,
98 thrift_protocol_functions
,
105 STANDARD_MODULE_PROPERTIES
108 #ifdef COMPILE_DL_THRIFT_PROTOCOL
109 ZEND_GET_MODULE(thrift_protocol
)
112 class PHPExceptionWrapper
: public std::exception
{
114 PHPExceptionWrapper(zval
* _ex
) throw() {
116 snprintf(_what
, 40, "PHP exception zval=%p", _ex
);
119 PHPExceptionWrapper(zend_object
* _exobj
) throw() {
120 ZVAL_OBJ(&ex
, _exobj
);
121 snprintf(_what
, 40, "PHP exception zval=%p", _exobj
);
123 ~PHPExceptionWrapper() throw() {
127 const char* what() const throw() {
130 operator zval
*() const throw() {
131 return const_cast<zval
*>(&ex
);
132 } // Zend API doesn't do 'const'...
140 PHPTransport(zval
* _p
, size_t _buffer_size
) {
141 assert(Z_TYPE_P(_p
) == IS_OBJECT
);
145 buffer
= reinterpret_cast<char*>(emalloc(_buffer_size
));
148 buffer_size
= _buffer_size
;
150 // Get the transport for the passed protocol
152 ZVAL_STRING(&gettransport
, "getTransport");
153 call_user_function(nullptr, _p
, &gettransport
, &t
, 0, nullptr);
155 zval_dtor(&gettransport
);
158 zend_object
*ex
= EG(exception
);
159 EG(exception
) = nullptr;
160 throw PHPExceptionWrapper(ex
);
163 assert(Z_TYPE(t
) == IS_OBJECT
);
180 class PHPOutputTransport
: public PHPTransport
{
182 PHPOutputTransport(zval
* _p
, size_t _buffer_size
= 8192) : PHPTransport(_p
, _buffer_size
) { }
183 ~PHPOutputTransport() { }
185 void write(const char* data
, size_t len
) {
186 if ((len
+ buffer_used
) > buffer_size
) {
189 if (len
> buffer_size
) {
190 directWrite(data
, len
);
192 memcpy(buffer_ptr
, data
, len
);
198 void writeI64(int64_t i
) {
200 write((const char*)&i
, 8);
203 void writeU32(uint32_t i
) {
205 write((const char*)&i
, 4);
208 void writeI32(int32_t i
) {
210 write((const char*)&i
, 4);
213 void writeI16(int16_t i
) {
215 write((const char*)&i
, 2);
218 void writeI8(int8_t i
) {
219 write((const char*)&i
, 1);
222 void writeString(const char* str
, size_t len
) {
233 void internalFlush() {
235 directWrite(buffer
, buffer_used
);
243 ZVAL_STRING(&flushfn
, "flush");
245 call_user_function(EG(function_table
), &(this->t
), &flushfn
, &ret
, 0, nullptr);
249 zend_object
*ex
= EG(exception
);
250 EG(exception
) = nullptr;
251 throw PHPExceptionWrapper(ex
);
254 void directWrite(const char* data
, size_t len
) {
255 zval args
[1], ret
, writefn
;
257 ZVAL_STRING(&writefn
, "write");
258 ZVAL_STRINGL(&args
[0], data
, len
);
261 call_user_function(EG(function_table
), &(this->t
), &writefn
, &ret
, 1, args
);
268 zend_object
*ex
= EG(exception
);
269 EG(exception
) = nullptr;
270 throw PHPExceptionWrapper(ex
);
275 class PHPInputTransport
: public PHPTransport
{
277 PHPInputTransport(zval
* _p
, size_t _buffer_size
= 8192) : PHPTransport(_p
, _buffer_size
) {
280 ~PHPInputTransport() {
286 zval args
[1], ret
, putbackfn
;
287 ZVAL_STRINGL(&args
[0], buffer_ptr
, buffer_used
);
288 ZVAL_STRING(&putbackfn
, "putBack");
291 call_user_function(EG(function_table
), &(this->t
), &putbackfn
, &ret
, 1, args
);
293 zval_dtor(&putbackfn
);
297 zend_object
*ex
= EG(exception
);
298 EG(exception
) = nullptr;
299 throw PHPExceptionWrapper(ex
);
306 void skip(size_t len
) {
308 size_t chunk_size
= (std::min
)(len
, buffer_used
);
310 buffer_ptr
= reinterpret_cast<char*>(buffer_ptr
) + chunk_size
;
311 buffer_used
-= chunk_size
;
319 void readBytes(void* buf
, size_t len
) {
321 size_t chunk_size
= (std::min
)(len
, buffer_used
);
323 memcpy(buf
, buffer_ptr
, chunk_size
);
324 buffer_ptr
= reinterpret_cast<char*>(buffer_ptr
) + chunk_size
;
325 buffer_used
-= chunk_size
;
326 buf
= reinterpret_cast<char*>(buf
) + chunk_size
;
343 return (int16_t)ntohs(c
);
349 return (uint32_t)ntohl(c
);
355 return (int32_t)ntohl(c
);
360 assert(buffer_used
== 0);
366 ZVAL_LONG(&args
[0], buffer_size
);
368 ZVAL_STRING(&funcname
, "read");
370 call_user_function(EG(function_table
), &(this->t
), &funcname
, &retval
, 1, args
);
372 zval_dtor(&funcname
);
377 zend_object
*ex
= EG(exception
);
378 EG(exception
) = nullptr;
379 throw PHPExceptionWrapper(ex
);
382 buffer_used
= Z_STRLEN(retval
);
383 memcpy(buffer
, Z_STRVAL(retval
), buffer_used
);
393 void binary_deserialize_spec(zval
* zthis
, PHPInputTransport
& transport
, HashTable
* spec
);
395 void binary_serialize_spec(zval
* zthis
, PHPOutputTransport
& transport
, HashTable
* spec
);
397 void binary_serialize(int8_t thrift_typeID
, PHPOutputTransport
& transport
, zval
* value
, HashTable
* fieldspec
);
399 bool ttype_is_scalar(int8_t t
);
401 // Create a PHP object given a typename and call the ctor, optionally passing up to 2 arguments
403 void createObject(const char* obj_typename
, zval
* return_value
, int nargs
= 0, zval
* arg1
= nullptr, zval
* arg2
= nullptr) {
404 /* is there a better way to do that on the stack ? */
405 zend_string
*obj_name
= zend_string_init(obj_typename
, strlen(obj_typename
), 0);
406 zend_class_entry
* ce
= zend_fetch_class(obj_name
, ZEND_FETCH_CLASS_DEFAULT
);
407 zend_string_release(obj_name
);
410 php_error_docref(nullptr, E_ERROR
, "Class %s does not exist", obj_typename
);
414 object_and_properties_init(return_value
, ce
, nullptr);
415 zend_function
* constructor
= zend_std_get_constructor(Z_OBJ_P(return_value
));
417 zend_call_method(return_value
, ce
, &constructor
, NULL
, 0, &ctor_rv
, nargs
, arg1
, arg2
);
420 zend_object
*ex
= EG(exception
);
421 EG(exception
) = nullptr;
422 throw PHPExceptionWrapper(ex
);
427 void throw_tprotocolexception(const char* what
, long errorcode
) {
428 zval zwhat
, zerrorcode
;
430 ZVAL_STRING(&zwhat
, what
);
431 ZVAL_LONG(&zerrorcode
, errorcode
);
434 createObject("\\Thrift\\Exception\\TProtocolException", &ex
, 2, &zwhat
, &zerrorcode
);
437 zval_dtor(&zerrorcode
);
439 throw PHPExceptionWrapper(&ex
);
442 // Sets EG(exception), call this and then RETURN_NULL();
444 void throw_zend_exception_from_std_exception(const std::exception
& ex
) {
445 zend_throw_exception(zend_exception_get_default(), const_cast<char*>(ex
.what()), 0);
449 void skip_element(long thrift_typeID
, PHPInputTransport
& transport
) {
450 switch (thrift_typeID
) {
456 int8_t ttype
= transport
.readI8(); // get field type
457 if (ttype
== T_STOP
) break;
458 transport
.skip(2); // skip field number, I16
459 skip_element(ttype
, transport
); // skip field payload
477 //case T_UTF7: // aliases T_STRING
481 uint32_t len
= transport
.readU32();
485 int8_t keytype
= transport
.readI8();
486 int8_t valtype
= transport
.readI8();
487 uint32_t size
= transport
.readU32();
488 for (uint32_t i
= 0; i
< size
; ++i
) {
489 skip_element(keytype
, transport
);
490 skip_element(valtype
, transport
);
495 int8_t valtype
= transport
.readI8();
496 uint32_t size
= transport
.readU32();
497 for (uint32_t i
= 0; i
< size
; ++i
) {
498 skip_element(valtype
, transport
);
504 sprintf(errbuf
, "Unknown thrift typeID %ld", thrift_typeID
);
505 throw_tprotocolexception(errbuf
, INVALID_DATA
);
509 bool zval_is_bool(zval
* v
) {
510 return Z_TYPE_P(v
) == IS_TRUE
|| Z_TYPE_P(v
) == IS_FALSE
;
514 void binary_deserialize(int8_t thrift_typeID
, PHPInputTransport
& transport
, zval
* return_value
, HashTable
* fieldspec
) {
515 ZVAL_NULL(return_value
);
517 switch (thrift_typeID
) {
523 zval
* val_ptr
= zend_hash_str_find(fieldspec
, "class", sizeof("class")-1);
524 if (val_ptr
== nullptr) {
525 throw_tprotocolexception("no class type in spec", INVALID_DATA
);
526 skip_element(T_STRUCT
, transport
);
530 char* structType
= Z_STRVAL_P(val_ptr
);
531 // Create an object in PHP userland based on our spec
532 createObject(structType
, return_value
);
533 if (Z_TYPE_P(return_value
) == IS_NULL
) {
534 // unable to create class entry
535 skip_element(T_STRUCT
, transport
);
539 zval
* spec
= zend_read_static_property(Z_OBJCE_P(return_value
), "_TSPEC", sizeof("_TSPEC")-1, false);
542 zend_object
*ex
= EG(exception
);
543 EG(exception
) = nullptr;
544 throw PHPExceptionWrapper(ex
);
546 if (Z_TYPE_P(spec
) != IS_ARRAY
) {
548 snprintf(errbuf
, 128, "spec for %s is wrong type: %d\n", structType
, Z_TYPE_P(spec
));
549 throw_tprotocolexception(errbuf
, INVALID_DATA
);
552 binary_deserialize_spec(return_value
, transport
, Z_ARRVAL_P(spec
));
557 transport
.readBytes(&c
, 1);
560 //case T_I08: // same numeric value as T_BYTE
563 transport
.readBytes(&c
, 1);
564 RETURN_LONG((int8_t)c
);
568 transport
.readBytes(&c
, 2);
569 RETURN_LONG((int16_t)ntohs(c
));
573 transport
.readBytes(&c
, 4);
574 RETURN_LONG((int32_t)ntohl(c
));
579 transport
.readBytes(&c
, 8);
580 RETURN_LONG((int64_t)ntohll(c
));
587 transport
.readBytes(&(a
.c
), 8);
591 //case T_UTF7: // aliases T_STRING
595 uint32_t size
= transport
.readU32();
598 transport
.readBytes(strbuf
, size
);
600 ZVAL_STRINGL(return_value
, strbuf
, size
);
602 ZVAL_EMPTY_STRING(return_value
);
606 case T_MAP
: { // array of key -> value
608 transport
.readBytes(types
, 2);
609 uint32_t size
= transport
.readU32();
610 array_init(return_value
);
613 val_ptr
= zend_hash_str_find(fieldspec
, "key", sizeof("key")-1);
614 HashTable
* keyspec
= Z_ARRVAL_P(val_ptr
);
615 val_ptr
= zend_hash_str_find(fieldspec
, "val", sizeof("val")-1);
616 HashTable
* valspec
= Z_ARRVAL_P(val_ptr
);
618 for (uint32_t s
= 0; s
< size
; ++s
) {
621 binary_deserialize(types
[0], transport
, &key
, keyspec
);
622 binary_deserialize(types
[1], transport
, &value
, valspec
);
623 if (Z_TYPE(key
) == IS_LONG
) {
624 zend_hash_index_update(Z_ARR_P(return_value
), Z_LVAL(key
), &value
);
626 if (Z_TYPE(key
) != IS_STRING
) convert_to_string(&key
);
627 zend_symtable_update(Z_ARR_P(return_value
), Z_STR(key
), &value
);
631 return; // return_value already populated
633 case T_LIST
: { // array with autogenerated numeric keys
634 int8_t type
= transport
.readI8();
635 uint32_t size
= transport
.readU32();
636 zval
*val_ptr
= zend_hash_str_find(fieldspec
, "elem", sizeof("elem")-1);
637 HashTable
* elemspec
= Z_ARRVAL_P(val_ptr
);
639 array_init(return_value
);
640 for (uint32_t s
= 0; s
< size
; ++s
) {
642 binary_deserialize(type
, transport
, &value
, elemspec
);
643 zend_hash_next_index_insert(Z_ARR_P(return_value
), &value
);
647 case T_SET
: { // array of key -> TRUE
650 transport
.readBytes(&type
, 1);
651 transport
.readBytes(&size
, 4);
653 zval
*val_ptr
= zend_hash_str_find(fieldspec
, "elem", sizeof("elem")-1);
654 HashTable
* elemspec
= Z_ARRVAL_P(val_ptr
);
656 array_init(return_value
);
658 for (uint32_t s
= 0; s
< size
; ++s
) {
662 binary_deserialize(type
, transport
, &key
, elemspec
);
664 if (Z_TYPE(key
) == IS_LONG
) {
665 zend_hash_index_update(Z_ARR_P(return_value
), Z_LVAL(key
), &value
);
667 if (Z_TYPE(key
) != IS_STRING
) convert_to_string(&key
);
668 zend_symtable_update(Z_ARR_P(return_value
), Z_STR(key
), &value
);
677 sprintf(errbuf
, "Unknown thrift typeID %d", thrift_typeID
);
678 throw_tprotocolexception(errbuf
, INVALID_DATA
);
682 void binary_serialize_hashtable_key(int8_t keytype
, PHPOutputTransport
& transport
, HashTable
* ht
, HashPosition
& ht_pos
, HashTable
* spec
) {
683 bool keytype_is_numeric
= (!((keytype
== T_STRING
) || (keytype
== T_UTF8
) || (keytype
== T_UTF16
)));
691 int res
= zend_hash_get_current_key_ex(ht
, &key
, (zend_ulong
*)&index
, &ht_pos
);
692 if (res
== HASH_KEY_IS_STRING
) {
693 ZVAL_STR_COPY(&z
, key
);
695 ZVAL_LONG(&z
, index
);
697 binary_serialize(keytype
, transport
, &z
, spec
);
702 void binary_serialize(int8_t thrift_typeID
, PHPOutputTransport
& transport
, zval
* value
, HashTable
* fieldspec
) {
706 // At this point the typeID (and field num, if applicable) should've already been written to the output so all we need to do is write the payload.
707 switch (thrift_typeID
) {
712 if (Z_TYPE_P(value
) != IS_OBJECT
) {
713 throw_tprotocolexception("Attempt to send non-object type as a T_STRUCT", INVALID_DATA
);
715 zval
* spec
= zend_read_static_property(Z_OBJCE_P(value
), "_TSPEC", sizeof("_TSPEC")-1, true);
716 if (spec
&& Z_TYPE_P(spec
) == IS_REFERENCE
) {
719 if (!spec
|| Z_TYPE_P(spec
) != IS_ARRAY
) {
720 throw_tprotocolexception("Attempt to send non-Thrift object as a T_STRUCT", INVALID_DATA
);
722 binary_serialize_spec(value
, transport
, Z_ARRVAL_P(spec
));
725 if (!zval_is_bool(value
)) convert_to_boolean(value
);
726 transport
.writeI8(Z_TYPE_INFO_P(value
) == IS_TRUE
? 1 : 0);
729 if (Z_TYPE_P(value
) != IS_LONG
) convert_to_long(value
);
730 transport
.writeI8(Z_LVAL_P(value
));
733 if (Z_TYPE_P(value
) != IS_LONG
) convert_to_long(value
);
734 transport
.writeI16(Z_LVAL_P(value
));
737 if (Z_TYPE_P(value
) != IS_LONG
) convert_to_long(value
);
738 transport
.writeI32(Z_LVAL_P(value
));
743 #if defined(_LP64) || defined(_WIN64)
744 if (Z_TYPE_P(value
) != IS_LONG
) convert_to_long(value
);
745 l_data
= Z_LVAL_P(value
);
747 if (Z_TYPE_P(value
) != IS_DOUBLE
) convert_to_double(value
);
748 l_data
= (int64_t)Z_DVAL_P(value
);
750 transport
.writeI64(l_data
);
757 if (Z_TYPE_P(value
) != IS_DOUBLE
) convert_to_double(value
);
758 a
.d
= Z_DVAL_P(value
);
759 transport
.writeI64(a
.c
);
764 if (Z_TYPE_P(value
) != IS_STRING
) convert_to_string(value
);
765 transport
.writeString(Z_STRVAL_P(value
), Z_STRLEN_P(value
));
768 if (Z_TYPE_P(value
) != IS_ARRAY
) convert_to_array(value
);
769 if (Z_TYPE_P(value
) != IS_ARRAY
) {
770 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_MAP)", INVALID_DATA
);
772 HashTable
* ht
= Z_ARRVAL_P(value
);
775 val_ptr
= zend_hash_str_find(fieldspec
, "ktype", sizeof("ktype")-1);
776 if (Z_TYPE_P(val_ptr
) != IS_LONG
) convert_to_long(val_ptr
);
777 uint8_t keytype
= Z_LVAL_P(val_ptr
);
778 transport
.writeI8(keytype
);
779 val_ptr
= zend_hash_str_find(fieldspec
, "vtype", sizeof("vtype")-1);
780 if (Z_TYPE_P(val_ptr
) != IS_LONG
) convert_to_long(val_ptr
);
781 uint8_t valtype
= Z_LVAL_P(val_ptr
);
782 transport
.writeI8(valtype
);
784 val_ptr
= zend_hash_str_find(fieldspec
, "val", sizeof("val")-1);
785 HashTable
* valspec
= Z_ARRVAL_P(val_ptr
);
786 HashTable
* keyspec
= Z_ARRVAL_P(zend_hash_str_find(fieldspec
, "key", sizeof("key")-1));
788 transport
.writeI32(zend_hash_num_elements(ht
));
789 HashPosition key_ptr
;
790 for (zend_hash_internal_pointer_reset_ex(ht
, &key_ptr
);
791 (val_ptr
= zend_hash_get_current_data_ex(ht
, &key_ptr
)) != nullptr;
792 zend_hash_move_forward_ex(ht
, &key_ptr
)) {
793 binary_serialize_hashtable_key(keytype
, transport
, ht
, key_ptr
, keyspec
);
794 binary_serialize(valtype
, transport
, val_ptr
, valspec
);
798 if (Z_TYPE_P(value
) != IS_ARRAY
) convert_to_array(value
);
799 if (Z_TYPE_P(value
) != IS_ARRAY
) {
800 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_LIST)", INVALID_DATA
);
802 HashTable
* ht
= Z_ARRVAL_P(value
);
805 val_ptr
= zend_hash_str_find(fieldspec
, "etype", sizeof("etype")-1);
806 if (Z_TYPE_P(val_ptr
) != IS_LONG
) convert_to_long(val_ptr
);
807 uint8_t valtype
= Z_LVAL_P(val_ptr
);
808 transport
.writeI8(valtype
);
810 val_ptr
= zend_hash_str_find(fieldspec
, "elem", sizeof("elem")-1);
811 HashTable
* valspec
= Z_ARRVAL_P(val_ptr
);
813 transport
.writeI32(zend_hash_num_elements(ht
));
814 HashPosition key_ptr
;
815 for (zend_hash_internal_pointer_reset_ex(ht
, &key_ptr
);
816 (val_ptr
= zend_hash_get_current_data_ex(ht
, &key_ptr
)) != nullptr;
817 zend_hash_move_forward_ex(ht
, &key_ptr
)) {
818 binary_serialize(valtype
, transport
, val_ptr
, valspec
);
822 if (Z_TYPE_P(value
) != IS_ARRAY
) convert_to_array(value
);
823 if (Z_TYPE_P(value
) != IS_ARRAY
) {
824 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_SET)", INVALID_DATA
);
826 HashTable
* ht
= Z_ARRVAL_P(value
);
829 val_ptr
= zend_hash_str_find(fieldspec
, "etype", sizeof("etype")-1);
830 HashTable
* spec
= Z_ARRVAL_P(zend_hash_str_find(fieldspec
, "elem", sizeof("elem")-1));
831 if (Z_TYPE_P(val_ptr
) != IS_LONG
) convert_to_long(val_ptr
);
832 uint8_t keytype
= Z_LVAL_P(val_ptr
);
833 transport
.writeI8(keytype
);
835 transport
.writeI32(zend_hash_num_elements(ht
));
836 HashPosition key_ptr
;
837 if(ttype_is_scalar(keytype
)){
838 for (zend_hash_internal_pointer_reset_ex(ht
, &key_ptr
);
839 (val_ptr
= zend_hash_get_current_data_ex(ht
, &key_ptr
)) != nullptr;
840 zend_hash_move_forward_ex(ht
, &key_ptr
)) {
841 binary_serialize_hashtable_key(keytype
, transport
, ht
, key_ptr
, spec
);
844 for (zend_hash_internal_pointer_reset_ex(ht
, &key_ptr
);
845 (val_ptr
= zend_hash_get_current_data_ex(ht
, &key_ptr
)) != nullptr;
846 zend_hash_move_forward_ex(ht
, &key_ptr
)) {
847 binary_serialize(keytype
, transport
, val_ptr
, spec
);
854 snprintf(errbuf
, 128, "Unknown thrift typeID %d", thrift_typeID
);
855 throw_tprotocolexception(errbuf
, INVALID_DATA
);
859 void protocol_writeMessageBegin(zval
* transport
, zend_string
* method_name
, int32_t msgtype
, int32_t seqID
) {
864 ZVAL_STR_COPY(&args
[0], method_name
);
865 ZVAL_LONG(&args
[1], msgtype
);
866 ZVAL_LONG(&args
[2], seqID
);
868 ZVAL_STRING(&writeMessagefn
, "writeMessageBegin");
870 call_user_function(EG(function_table
), transport
, &writeMessagefn
, &ret
, 3, args
);
872 zval_dtor(&writeMessagefn
);
873 zval_dtor(&args
[2]); zval_dtor(&args
[1]); zval_dtor(&args
[0]);
876 zend_object
*ex
= EG(exception
);
877 EG(exception
) = nullptr;
878 throw PHPExceptionWrapper(ex
);
883 bool ttype_is_int(int8_t t
) {
884 return ((t
== T_BYTE
) || ((t
>= T_I16
) && (t
<= T_I64
)));
887 bool ttype_is_scalar(int8_t t
) {
888 return !((t
== T_STRUCT
) || ( t
== T_MAP
) || (t
== T_SET
) || (t
== T_LIST
));
892 bool ttypes_are_compatible(int8_t t1
, int8_t t2
) {
893 // Integer types of different widths are considered compatible;
894 // otherwise the typeID must match.
895 return ((t1
== t2
) || (ttype_is_int(t1
) && ttype_is_int(t2
)));
898 //is used to validate objects before serialization and after deserialization. For now, only required fields are validated.
900 void validate_thrift_object(zval
* object
) {
901 zend_class_entry
* object_class_entry
= Z_OBJCE_P(object
);
902 zval
* is_validate
= zend_read_static_property(object_class_entry
, "isValidate", sizeof("isValidate")-1, true);
904 ZVAL_DEREF(is_validate
);
906 zval
* spec
= zend_read_static_property(object_class_entry
, "_TSPEC", sizeof("_TSPEC")-1, true);
910 HashPosition key_ptr
;
913 if (is_validate
&& Z_TYPE_INFO_P(is_validate
) == IS_TRUE
) {
914 for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(spec
), &key_ptr
);
915 (val_ptr
= zend_hash_get_current_data_ex(Z_ARRVAL_P(spec
), &key_ptr
)) != nullptr;
916 zend_hash_move_forward_ex(Z_ARRVAL_P(spec
), &key_ptr
)) {
919 if (zend_hash_get_current_key_ex(Z_ARRVAL_P(spec
), nullptr, &fieldno
, &key_ptr
) != HASH_KEY_IS_LONG
) {
920 throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA
);
923 HashTable
* fieldspec
= Z_ARRVAL_P(val_ptr
);
926 zval
* zvarname
= zend_hash_str_find(fieldspec
, "var", sizeof("var")-1);
927 char* varname
= Z_STRVAL_P(zvarname
);
929 zval
* is_required
= zend_hash_str_find(fieldspec
, "isRequired", sizeof("isRequired")-1);
931 zval
* prop
= zend_read_property(object_class_entry
, object
, varname
, strlen(varname
), false, &rv
);
933 if (Z_TYPE_INFO_P(is_required
) == IS_TRUE
&& Z_TYPE_P(prop
) == IS_NULL
) {
935 snprintf(errbuf
, 128, "Required field %s.%s is unset!", ZSTR_VAL(object_class_entry
->name
), varname
);
936 throw_tprotocolexception(errbuf
, INVALID_DATA
);
943 void binary_deserialize_spec(zval
* zthis
, PHPInputTransport
& transport
, HashTable
* spec
) {
944 // SET and LIST have 'elem' => array('type', [optional] 'class')
945 // MAP has 'val' => array('type', [optiona] 'class')
946 zend_class_entry
* ce
= Z_OBJCE_P(zthis
);
948 int8_t ttype
= transport
.readI8();
949 if (ttype
== T_STOP
) {
950 validate_thrift_object(zthis
);
954 int16_t fieldno
= transport
.readI16();
955 zval
* val_ptr
= zend_hash_index_find(spec
, fieldno
);
956 if (val_ptr
!= nullptr) {
957 HashTable
* fieldspec
= Z_ARRVAL_P(val_ptr
);
958 // pull the field name
959 val_ptr
= zend_hash_str_find(fieldspec
, "var", sizeof("var")-1);
960 char* varname
= Z_STRVAL_P(val_ptr
);
963 val_ptr
= zend_hash_str_find(fieldspec
, "type", sizeof("type")-1);
964 if (Z_TYPE_P(val_ptr
) != IS_LONG
) convert_to_long(val_ptr
);
965 int8_t expected_ttype
= Z_LVAL_P(val_ptr
);
967 if (ttypes_are_compatible(ttype
, expected_ttype
)) {
971 binary_deserialize(ttype
, transport
, &rv
, fieldspec
);
972 zend_update_property(ce
, zthis
, varname
, strlen(varname
), &rv
);
976 skip_element(ttype
, transport
);
979 skip_element(ttype
, transport
);
985 void binary_serialize_spec(zval
* zthis
, PHPOutputTransport
& transport
, HashTable
* spec
) {
987 validate_thrift_object(zthis
);
989 HashPosition key_ptr
;
992 for (zend_hash_internal_pointer_reset_ex(spec
, &key_ptr
);
993 (val_ptr
= zend_hash_get_current_data_ex(spec
, &key_ptr
)) != nullptr;
994 zend_hash_move_forward_ex(spec
, &key_ptr
)) {
997 if (zend_hash_get_current_key_ex(spec
, nullptr, &fieldno
, &key_ptr
) != HASH_KEY_IS_LONG
) {
998 throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA
);
1001 HashTable
* fieldspec
= Z_ARRVAL_P(val_ptr
);
1004 val_ptr
= zend_hash_str_find(fieldspec
, "var", sizeof("var")-1);
1005 char* varname
= Z_STRVAL_P(val_ptr
);
1008 val_ptr
= zend_hash_str_find(fieldspec
, "type", sizeof("type")-1);
1009 if (Z_TYPE_P(val_ptr
) != IS_LONG
) convert_to_long(val_ptr
);
1010 int8_t ttype
= Z_LVAL_P(val_ptr
);
1013 zval
* prop
= zend_read_property(Z_OBJCE_P(zthis
), zthis
, varname
, strlen(varname
), false, &rv
);
1015 if (Z_TYPE_P(prop
) == IS_REFERENCE
){
1018 if (Z_TYPE_P(prop
) != IS_NULL
) {
1019 transport
.writeI8(ttype
);
1020 transport
.writeI16(fieldno
);
1021 binary_serialize(ttype
, transport
, prop
, fieldspec
);
1024 transport
.writeI8(T_STOP
); // struct end
1027 // 6 params: $transport $method_name $ttype $request_struct $seqID $strict_write
1028 PHP_FUNCTION(thrift_protocol_write_binary
) {
1030 zval
*request_struct
;
1031 zend_string
*method_name
;
1032 long msgtype
, seqID
;
1033 zend_bool strict_write
;
1035 if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET
, ZEND_NUM_ARGS(), "oSlolb",
1036 &protocol
, &method_name
, &msgtype
,
1037 &request_struct
, &seqID
, &strict_write
) == FAILURE
) {
1042 zval
* spec
= zend_read_static_property(Z_OBJCE_P(request_struct
), "_TSPEC", sizeof("_TSPEC")-1, true);
1047 if (!spec
|| Z_TYPE_P(spec
) != IS_ARRAY
) {
1048 throw_tprotocolexception("Attempt serialize from non-Thrift object", INVALID_DATA
);
1051 PHPOutputTransport
transport(protocol
);
1052 protocol_writeMessageBegin(protocol
, method_name
, (int32_t) msgtype
, (int32_t) seqID
);
1053 binary_serialize_spec(request_struct
, transport
, Z_ARRVAL_P(spec
));
1056 } catch (const PHPExceptionWrapper
& ex
) {
1057 // ex will be destructed, so copy to a zval that zend_throw_exception_object can take ownership of
1059 ZVAL_COPY(&myex
, ex
);
1060 zend_throw_exception_object(&myex
);
1062 } catch (const std::exception
& ex
) {
1063 throw_zend_exception_from_std_exception(ex
);
1069 // 4 params: $transport $response_Typename $strict_read $buffer_size
1070 PHP_FUNCTION(thrift_protocol_read_binary
) {
1072 zend_string
*obj_typename
;
1073 zend_bool strict_read
;
1074 size_t buffer_size
= 8192;
1076 if (zend_parse_parameters(ZEND_NUM_ARGS(), "oSb|l", &protocol
, &obj_typename
, &strict_read
, &buffer_size
) == FAILURE
) {
1081 PHPInputTransport
transport(protocol
, buffer_size
);
1082 int8_t messageType
= 0;
1083 int32_t sz
= transport
.readI32();
1086 // Check for correct version number
1087 int32_t version
= sz
& VERSION_MASK
;
1088 if (version
!= VERSION_1
) {
1089 throw_tprotocolexception("Bad version identifier", BAD_VERSION
);
1091 messageType
= (sz
& 0x000000ff);
1092 int32_t namelen
= transport
.readI32();
1093 // skip the name string and the sequence ID, we don't care about those
1094 transport
.skip(namelen
+ 4);
1097 throw_tprotocolexception("No version identifier... old protocol client in strict mode?", BAD_VERSION
);
1099 // Handle pre-versioned input
1100 transport
.skip(sz
); // skip string body
1101 messageType
= transport
.readI8();
1102 transport
.skip(4); // skip sequence number
1106 if (messageType
== T_EXCEPTION
) {
1108 createObject("\\Thrift\\Exception\\TApplicationException", &ex
);
1109 zval
* spec
= zend_read_static_property(Z_OBJCE(ex
), "_TSPEC", sizeof("_TPSEC")-1, false);
1111 if (EG(exception
)) {
1112 zend_object
*ex
= EG(exception
);
1113 EG(exception
) = nullptr;
1114 throw PHPExceptionWrapper(ex
);
1116 binary_deserialize_spec(&ex
, transport
, Z_ARRVAL_P(spec
));
1117 throw PHPExceptionWrapper(&ex
);
1120 createObject(ZSTR_VAL(obj_typename
), return_value
);
1121 zval
* spec
= zend_read_static_property(Z_OBJCE_P(return_value
), "_TSPEC", sizeof("_TSPEC")-1, true);
1125 if (!spec
|| Z_TYPE_P(spec
) != IS_ARRAY
) {
1126 throw_tprotocolexception("Attempt deserialize to non-Thrift object", INVALID_DATA
);
1128 binary_deserialize_spec(return_value
, transport
, Z_ARRVAL_P(spec
));
1129 } catch (const PHPExceptionWrapper
& ex
) {
1130 // ex will be destructed, so copy to a zval that zend_throw_exception_object can ownership of
1132 ZVAL_COPY(&myex
, ex
);
1133 zval_dtor(return_value
);
1134 zend_throw_exception_object(&myex
);
1136 } catch (const std::exception
& ex
) {
1137 throw_zend_exception_from_std_exception(ex
);
1142 // 4 params: $transport $response_Typename $strict_read $buffer_size
1143 PHP_FUNCTION(thrift_protocol_read_binary_after_message_begin
) {
1145 zend_string
*obj_typename
;
1146 zend_bool strict_read
;
1147 size_t buffer_size
= 8192;
1149 if (zend_parse_parameters(ZEND_NUM_ARGS(), "oSb|l", &protocol
, &obj_typename
, &strict_read
, &buffer_size
) == FAILURE
) {
1154 PHPInputTransport
transport(protocol
, buffer_size
);
1156 createObject(ZSTR_VAL(obj_typename
), return_value
);
1157 zval
* spec
= zend_read_static_property(Z_OBJCE_P(return_value
), "_TSPEC", sizeof("_TSPEC")-1, false);
1159 binary_deserialize_spec(return_value
, transport
, Z_ARRVAL_P(spec
));
1160 } catch (const PHPExceptionWrapper
& ex
) {
1161 // ex will be destructed, so copy to a zval that zend_throw_exception_object can take ownership of
1163 ZVAL_COPY(&myex
, ex
);
1164 zend_throw_exception_object(&myex
);
1166 } catch (const std::exception
& ex
) {
1167 throw_zend_exception_from_std_exception(ex
);
1172 #endif /* PHP_VERSION_ID >= 70000 */