1 /*
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
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
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
17 * under the License.
18 */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "php.h"
24 #include "zend_interfaces.h"
25 #include "zend_exceptions.h"
26 #include "php_thrift_protocol.h"
27
28 #if PHP_VERSION_ID >= 70000
29
30 #include <sys/types.h>
31 #include <arpa/inet.h>
32
33 #include <cstdint>
34 #include <stdexcept>
35 #include <algorithm>
36
37 #ifndef bswap_64
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))
46 #endif
47
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
52 #define htonll(x) x
53 #define ntohll(x) x
54 #else
55 #error Unknown __BYTE_ORDER
56 #endif
57
58 enum TType {
59 T_STOP = 0,
60 T_VOID = 1,
61 T_BOOL = 2,
62 T_BYTE = 3,
63 T_I08 = 3,
64 T_I16 = 6,
65 T_I32 = 8,
66 T_U64 = 9,
67 T_I64 = 10,
68 T_DOUBLE = 4,
69 T_STRING = 11,
70 T_UTF7 = 11,
71 T_STRUCT = 12,
72 T_MAP = 13,
73 T_SET = 14,
74 T_LIST = 15,
75 T_UTF8 = 16,
76 T_UTF16 = 17
77 };
78
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;
84 // tprotocolexception
85 const int INVALID_DATA = 1;
86 const int BAD_VERSION = 4;
87
88 zend_module_entry thrift_protocol_module_entry = {
89 STANDARD_MODULE_HEADER,
90 "thrift_protocol",
91 ext_functions,
92 nullptr,
93 nullptr,
94 nullptr,
95 nullptr,
96 nullptr,
97 "1.0",
98 STANDARD_MODULE_PROPERTIES
99 };
100
101 #ifdef COMPILE_DL_THRIFT_PROTOCOL
102 ZEND_GET_MODULE(thrift_protocol)
103 #endif
104
105 class PHPExceptionWrapper : public std::exception {
106 public:
PHPExceptionWrapper(zval * _ex)107 PHPExceptionWrapper(zval* _ex) throw() {
108 ZVAL_COPY(&ex, _ex);
109 snprintf(_what, 40, "PHP exception zval=%p", _ex);
110 }
111
PHPExceptionWrapper(zend_object * _exobj)112 PHPExceptionWrapper(zend_object* _exobj) throw() {
113 ZVAL_OBJ(&ex, _exobj);
114 snprintf(_what, 40, "PHP exception zval=%p", _exobj);
115 }
~PHPExceptionWrapper()116 ~PHPExceptionWrapper() throw() {
117 zval_dtor(&ex);
118 }
119
what() const120 const char* what() const throw() {
121 return _what;
122 }
operator zval*() const123 operator zval*() const throw() {
124 return const_cast<zval*>(&ex);
125 } // Zend API doesn't do 'const'...
126 protected:
127 zval ex;
128 char _what[40];
129 } ;
130
131 class PHPTransport {
132 protected:
PHPTransport(zval * _p,size_t _buffer_size)133 PHPTransport(zval* _p, size_t _buffer_size) {
134 assert(Z_TYPE_P(_p) == IS_OBJECT);
135
136 ZVAL_UNDEF(&t);
137
138 buffer = reinterpret_cast<char*>(emalloc(_buffer_size));
139 buffer_ptr = buffer;
140 buffer_used = 0;
141 buffer_size = _buffer_size;
142
143 // Get the transport for the passed protocol
144 zval gettransport;
145 ZVAL_STRING(&gettransport, "getTransport");
146 call_user_function(nullptr, _p, &gettransport, &t, 0, nullptr);
147
148 zval_dtor(&gettransport);
149
150 if (EG(exception)) {
151 zend_object *ex = EG(exception);
152 EG(exception) = nullptr;
153 throw PHPExceptionWrapper(ex);
154 }
155
156 assert(Z_TYPE(t) == IS_OBJECT);
157 }
158
~PHPTransport()159 ~PHPTransport() {
160 efree(buffer);
161 zval_dtor(&t);
162 }
163
164 char* buffer;
165 char* buffer_ptr;
166 size_t buffer_used;
167 size_t buffer_size;
168
169 zval t;
170 };
171
172
173 class PHPOutputTransport : public PHPTransport {
174 public:
PHPOutputTransport(zval * _p,size_t _buffer_size=8192)175 PHPOutputTransport(zval* _p, size_t _buffer_size = 8192) : PHPTransport(_p, _buffer_size) { }
~PHPOutputTransport()176 ~PHPOutputTransport() { }
177
write(const char * data,size_t len)178 void write(const char* data, size_t len) {
179 if ((len + buffer_used) > buffer_size) {
180 internalFlush();
181 }
182 if (len > buffer_size) {
183 directWrite(data, len);
184 } else {
185 memcpy(buffer_ptr, data, len);
186 buffer_used += len;
187 buffer_ptr += len;
188 }
189 }
190
writeI64(int64_t i)191 void writeI64(int64_t i) {
192 i = htonll(i);
193 write((const char*)&i, 8);
194 }
195
writeU32(uint32_t i)196 void writeU32(uint32_t i) {
197 i = htonl(i);
198 write((const char*)&i, 4);
199 }
200
writeI32(int32_t i)201 void writeI32(int32_t i) {
202 i = htonl(i);
203 write((const char*)&i, 4);
204 }
205
writeI16(int16_t i)206 void writeI16(int16_t i) {
207 i = htons(i);
208 write((const char*)&i, 2);
209 }
210
writeI8(int8_t i)211 void writeI8(int8_t i) {
212 write((const char*)&i, 1);
213 }
214
writeString(const char * str,size_t len)215 void writeString(const char* str, size_t len) {
216 writeU32(len);
217 write(str, len);
218 }
219
flush()220 void flush() {
221 internalFlush();
222 directFlush();
223 }
224
225 protected:
internalFlush()226 void internalFlush() {
227 if (buffer_used) {
228 directWrite(buffer, buffer_used);
229 buffer_ptr = buffer;
230 buffer_used = 0;
231 }
232 }
directFlush()233 void directFlush() {
234 zval ret, flushfn;
235 ZVAL_NULL(&ret);
236 ZVAL_STRING(&flushfn, "flush");
237
238 call_user_function(EG(function_table), &(this->t), &flushfn, &ret, 0, nullptr);
239 zval_dtor(&flushfn);
240 zval_dtor(&ret);
241 if (EG(exception)) {
242 zend_object *ex = EG(exception);
243 EG(exception) = nullptr;
244 throw PHPExceptionWrapper(ex);
245 }
246 }
directWrite(const char * data,size_t len)247 void directWrite(const char* data, size_t len) {
248 zval args[1], ret, writefn;
249
250 ZVAL_STRING(&writefn, "write");
251 ZVAL_STRINGL(&args[0], data, len);
252
253 ZVAL_NULL(&ret);
254 call_user_function(EG(function_table), &(this->t), &writefn, &ret, 1, args);
255
256 zval_dtor(&writefn);
257 zval_dtor(&ret);
258 zval_dtor(&args[0]);
259
260 if (EG(exception)) {
261 zend_object *ex = EG(exception);
262 EG(exception) = nullptr;
263 throw PHPExceptionWrapper(ex);
264 }
265 }
266 };
267
268 class PHPInputTransport : public PHPTransport {
269 public:
PHPInputTransport(zval * _p,size_t _buffer_size=8192)270 PHPInputTransport(zval* _p, size_t _buffer_size = 8192) : PHPTransport(_p, _buffer_size) {
271 }
272
~PHPInputTransport()273 ~PHPInputTransport() {
274 put_back();
275 }
276
put_back()277 void put_back() {
278 if (buffer_used) {
279 zval args[1], ret, putbackfn;
280 ZVAL_STRINGL(&args[0], buffer_ptr, buffer_used);
281 ZVAL_STRING(&putbackfn, "putBack");
282 ZVAL_NULL(&ret);
283
284 call_user_function(EG(function_table), &(this->t), &putbackfn, &ret, 1, args);
285
286 zval_dtor(&putbackfn);
287 zval_dtor(&ret);
288 zval_dtor(&args[0]);
289 if (EG(exception)) {
290 zend_object *ex = EG(exception);
291 EG(exception) = nullptr;
292 throw PHPExceptionWrapper(ex);
293 }
294 }
295 buffer_used = 0;
296 buffer_ptr = buffer;
297 }
298
skip(size_t len)299 void skip(size_t len) {
300 while (len) {
301 size_t chunk_size = (std::min)(len, buffer_used);
302 if (chunk_size) {
303 buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size;
304 buffer_used -= chunk_size;
305 len -= chunk_size;
306 }
307 if (! len) break;
308 refill();
309 }
310 }
311
readBytes(void * buf,size_t len)312 void readBytes(void* buf, size_t len) {
313 while (len) {
314 size_t chunk_size = (std::min)(len, buffer_used);
315 if (chunk_size) {
316 memcpy(buf, buffer_ptr, chunk_size);
317 buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size;
318 buffer_used -= chunk_size;
319 buf = reinterpret_cast<char*>(buf) + chunk_size;
320 len -= chunk_size;
321 }
322 if (! len) break;
323 refill();
324 }
325 }
326
readI8()327 int8_t readI8() {
328 int8_t c;
329 readBytes(&c, 1);
330 return c;
331 }
332
readI16()333 int16_t readI16() {
334 int16_t c;
335 readBytes(&c, 2);
336 return (int16_t)ntohs(c);
337 }
338
readU32()339 uint32_t readU32() {
340 uint32_t c;
341 readBytes(&c, 4);
342 return (uint32_t)ntohl(c);
343 }
344
readI32()345 int32_t readI32() {
346 int32_t c;
347 readBytes(&c, 4);
348 return (int32_t)ntohl(c);
349 }
350
351 protected:
refill()352 void refill() {
353 assert(buffer_used == 0);
354 zval retval;
355 zval args[1];
356 zval funcname;
357
358 ZVAL_NULL(&retval);
359 ZVAL_LONG(&args[0], buffer_size);
360
361 ZVAL_STRING(&funcname, "read");
362
363 call_user_function(EG(function_table), &(this->t), &funcname, &retval, 1, args);
364 zval_dtor(&args[0]);
365 zval_dtor(&funcname);
366
367 if (EG(exception)) {
368 zval_dtor(&retval);
369
370 zend_object *ex = EG(exception);
371 EG(exception) = nullptr;
372 throw PHPExceptionWrapper(ex);
373 }
374
375 buffer_used = Z_STRLEN(retval);
376 memcpy(buffer, Z_STRVAL(retval), buffer_used);
377
378 zval_dtor(&retval);
379
380 buffer_ptr = buffer;
381 }
382
383 };
384
385 static
386 void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec);
387 static
388 void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec);
389 static
390 void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval* value, HashTable* fieldspec);
391 static inline
392 bool ttype_is_scalar(int8_t t);
393
394 // Create a PHP object given a typename and call the ctor, optionally passing up to 2 arguments
395 static
createObject(const char * obj_typename,zval * return_value,int nargs=0,zval * arg1=nullptr,zval * arg2=nullptr)396 void createObject(const char* obj_typename, zval* return_value, int nargs = 0, zval* arg1 = nullptr, zval* arg2 = nullptr) {
397 /* is there a better way to do that on the stack ? */
398 zend_string *obj_name = zend_string_init(obj_typename, strlen(obj_typename), 0);
399 zend_class_entry* ce = zend_fetch_class(obj_name, ZEND_FETCH_CLASS_DEFAULT);
400 zend_string_release(obj_name);
401
402 if (! ce) {
403 php_error_docref(nullptr, E_ERROR, "Class %s does not exist", obj_typename);
404 RETURN_NULL();
405 }
406
407 object_and_properties_init(return_value, ce, nullptr);
408 zend_function* constructor = zend_std_get_constructor(Z_OBJ_P(return_value));
409 zval ctor_rv;
410 zend_call_method(Z4_OBJ_P(return_value), ce, &constructor, nullptr, 0, &ctor_rv, nargs, arg1, arg2);
411 zval_dtor(&ctor_rv);
412 if (EG(exception)) {
413 zend_object *ex = EG(exception);
414 EG(exception) = nullptr;
415 throw PHPExceptionWrapper(ex);
416 }
417 }
418
419 static
throw_tprotocolexception(const char * what,long errorcode)420 void throw_tprotocolexception(const char* what, long errorcode) {
421 zval zwhat, zerrorcode;
422
423 ZVAL_STRING(&zwhat, what);
424 ZVAL_LONG(&zerrorcode, errorcode);
425
426 zval ex;
427 createObject("\\Thrift\\Exception\\TProtocolException", &ex, 2, &zwhat, &zerrorcode);
428
429 zval_dtor(&zwhat);
430 zval_dtor(&zerrorcode);
431
432 throw PHPExceptionWrapper(&ex);
433 }
434
435 // Sets EG(exception), call this and then RETURN_NULL();
436 static
throw_zend_exception_from_std_exception(const std::exception & ex)437 void throw_zend_exception_from_std_exception(const std::exception& ex) {
438 zend_throw_exception(zend_exception_get_default(), const_cast<char*>(ex.what()), 0);
439 }
440
441 static
skip_element(long thrift_typeID,PHPInputTransport & transport)442 void skip_element(long thrift_typeID, PHPInputTransport& transport) {
443 switch (thrift_typeID) {
444 case T_STOP:
445 case T_VOID:
446 return;
447 case T_STRUCT:
448 while (true) {
449 int8_t ttype = transport.readI8(); // get field type
450 if (ttype == T_STOP) break;
451 transport.skip(2); // skip field number, I16
452 skip_element(ttype, transport); // skip field payload
453 }
454 return;
455 case T_BOOL:
456 case T_BYTE:
457 transport.skip(1);
458 return;
459 case T_I16:
460 transport.skip(2);
461 return;
462 case T_I32:
463 transport.skip(4);
464 return;
465 case T_U64:
466 case T_I64:
467 case T_DOUBLE:
468 transport.skip(8);
469 return;
470 //case T_UTF7: // aliases T_STRING
471 case T_UTF8:
472 case T_UTF16:
473 case T_STRING: {
474 uint32_t len = transport.readU32();
475 transport.skip(len);
476 } return;
477 case T_MAP: {
478 int8_t keytype = transport.readI8();
479 int8_t valtype = transport.readI8();
480 uint32_t size = transport.readU32();
481 for (uint32_t i = 0; i < size; ++i) {
482 skip_element(keytype, transport);
483 skip_element(valtype, transport);
484 }
485 } return;
486 case T_LIST:
487 case T_SET: {
488 int8_t valtype = transport.readI8();
489 uint32_t size = transport.readU32();
490 for (uint32_t i = 0; i < size; ++i) {
491 skip_element(valtype, transport);
492 }
493 } return;
494 };
495
496 char errbuf[128];
497 sprintf(errbuf, "Unknown thrift typeID %ld", thrift_typeID);
498 throw_tprotocolexception(errbuf, INVALID_DATA);
499 }
500
501 static inline
zval_is_bool(zval * v)502 bool zval_is_bool(zval* v) {
503 return Z_TYPE_P(v) == IS_TRUE || Z_TYPE_P(v) == IS_FALSE;
504 }
505
506 static
binary_deserialize(int8_t thrift_typeID,PHPInputTransport & transport,zval * return_value,HashTable * fieldspec)507 void binary_deserialize(int8_t thrift_typeID, PHPInputTransport& transport, zval* return_value, HashTable* fieldspec) {
508 ZVAL_NULL(return_value);
509
510 switch (thrift_typeID) {
511 case T_STOP:
512 case T_VOID:
513 RETURN_NULL();
514 return;
515 case T_STRUCT: {
516 zval* val_ptr = zend_hash_str_find(fieldspec, "class", sizeof("class")-1);
517 if (val_ptr == nullptr) {
518 throw_tprotocolexception("no class type in spec", INVALID_DATA);
519 skip_element(T_STRUCT, transport);
520 RETURN_NULL();
521 }
522
523 char* structType = Z_STRVAL_P(val_ptr);
524 // Create an object in PHP userland based on our spec
525 createObject(structType, return_value);
526 if (Z_TYPE_P(return_value) == IS_NULL) {
527 // unable to create class entry
528 skip_element(T_STRUCT, transport);
529 RETURN_NULL();
530 }
531
532 zval* spec = zend_read_static_property(Z_OBJCE_P(return_value), "_TSPEC", sizeof("_TSPEC")-1, false);
533 ZVAL_DEREF(spec);
534 if (EG(exception)) {
535 zend_object *ex = EG(exception);
536 EG(exception) = nullptr;
537 throw PHPExceptionWrapper(ex);
538 }
539 if (Z_TYPE_P(spec) != IS_ARRAY) {
540 char errbuf[128];
541 snprintf(errbuf, 128, "spec for %s is wrong type: %d\n", structType, Z_TYPE_P(spec));
542 throw_tprotocolexception(errbuf, INVALID_DATA);
543 RETURN_NULL();
544 }
545 binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
546 return;
547 } break;
548 case T_BOOL: {
549 uint8_t c;
550 transport.readBytes(&c, 1);
551 RETURN_BOOL(c != 0);
552 }
553 //case T_I08: // same numeric value as T_BYTE
554 case T_BYTE: {
555 uint8_t c;
556 transport.readBytes(&c, 1);
557 RETURN_LONG((int8_t)c);
558 }
559 case T_I16: {
560 uint16_t c;
561 transport.readBytes(&c, 2);
562 RETURN_LONG((int16_t)ntohs(c));
563 }
564 case T_I32: {
565 uint32_t c;
566 transport.readBytes(&c, 4);
567 RETURN_LONG((int32_t)ntohl(c));
568 }
569 case T_U64:
570 case T_I64: {
571 uint64_t c;
572 transport.readBytes(&c, 8);
573 RETURN_LONG((int64_t)ntohll(c));
574 }
575 case T_DOUBLE: {
576 union {
577 uint64_t c;
578 double d;
579 } a;
580 transport.readBytes(&(a.c), 8);
581 a.c = ntohll(a.c);
582 RETURN_DOUBLE(a.d);
583 }
584 //case T_UTF7: // aliases T_STRING
585 case T_UTF8:
586 case T_UTF16:
587 case T_STRING: {
588 uint32_t size = transport.readU32();
589 if (size) {
590 char strbuf[size+1];
591 transport.readBytes(strbuf, size);
592 strbuf[size] = '\0';
593 ZVAL_STRINGL(return_value, strbuf, size);
594 } else {
595 ZVAL_EMPTY_STRING(return_value);
596 }
597 return;
598 }
599 case T_MAP: { // array of key -> value
600 uint8_t types[2];
601 transport.readBytes(types, 2);
602 uint32_t size = transport.readU32();
603 array_init(return_value);
604
605 zval *val_ptr;
606 val_ptr = zend_hash_str_find(fieldspec, "key", sizeof("key")-1);
607 HashTable* keyspec = Z_ARRVAL_P(val_ptr);
608 val_ptr = zend_hash_str_find(fieldspec, "val", sizeof("val")-1);
609 HashTable* valspec = Z_ARRVAL_P(val_ptr);
610
611 for (uint32_t s = 0; s < size; ++s) {
612 zval key, value;
613
614 binary_deserialize(types[0], transport, &key, keyspec);
615 binary_deserialize(types[1], transport, &value, valspec);
616 if (Z_TYPE(key) == IS_LONG) {
617 zend_hash_index_update(Z_ARR_P(return_value), Z_LVAL(key), &value);
618 } else {
619 if (Z_TYPE(key) != IS_STRING) convert_to_string(&key);
620 zend_symtable_update(Z_ARR_P(return_value), Z_STR(key), &value);
621 }
622 zval_dtor(&key);
623 }
624 return; // return_value already populated
625 }
626 case T_LIST: { // array with autogenerated numeric keys
627 int8_t type = transport.readI8();
628 uint32_t size = transport.readU32();
629 zval *val_ptr = zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1);
630 HashTable* elemspec = Z_ARRVAL_P(val_ptr);
631
632 array_init(return_value);
633 for (uint32_t s = 0; s < size; ++s) {
634 zval value;
635 binary_deserialize(type, transport, &value, elemspec);
636 zend_hash_next_index_insert(Z_ARR_P(return_value), &value);
637 }
638 return;
639 }
640 case T_SET: { // array of key -> TRUE
641 uint8_t type;
642 uint32_t size;
643 transport.readBytes(&type, 1);
644 transport.readBytes(&size, 4);
645 size = ntohl(size);
646 zval *val_ptr = zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1);
647 HashTable* elemspec = Z_ARRVAL_P(val_ptr);
648
649 array_init(return_value);
650
651 for (uint32_t s = 0; s < size; ++s) {
652 zval key, value;
653 ZVAL_TRUE(&value);
654
655 binary_deserialize(type, transport, &key, elemspec);
656
657 if (Z_TYPE(key) == IS_LONG) {
658 zend_hash_index_update(Z_ARR_P(return_value), Z_LVAL(key), &value);
659 } else {
660 if (Z_TYPE(key) != IS_STRING) convert_to_string(&key);
661 zend_symtable_update(Z_ARR_P(return_value), Z_STR(key), &value);
662 }
663 zval_dtor(&key);
664 }
665 return;
666 }
667 };
668
669 char errbuf[128];
670 sprintf(errbuf, "Unknown thrift typeID %d", thrift_typeID);
671 throw_tprotocolexception(errbuf, INVALID_DATA);
672 }
673
674 static
binary_serialize_hashtable_key(int8_t keytype,PHPOutputTransport & transport,HashTable * ht,HashPosition & ht_pos,HashTable * spec)675 void binary_serialize_hashtable_key(int8_t keytype, PHPOutputTransport& transport, HashTable* ht, HashPosition& ht_pos, HashTable* spec) {
676 bool keytype_is_numeric = (!((keytype == T_STRING) || (keytype == T_UTF8) || (keytype == T_UTF16)));
677
678 zend_string* key;
679 uint key_len;
680 long index = 0;
681
682 zval z;
683
684 int res = zend_hash_get_current_key_ex(ht, &key, (zend_ulong*)&index, &ht_pos);
685 if (res == HASH_KEY_IS_STRING) {
686 ZVAL_STR_COPY(&z, key);
687 } else {
688 ZVAL_LONG(&z, index);
689 }
690 binary_serialize(keytype, transport, &z, spec);
691 zval_dtor(&z);
692 }
693
694 static
binary_serialize(int8_t thrift_typeID,PHPOutputTransport & transport,zval * value,HashTable * fieldspec)695 void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval* value, HashTable* fieldspec) {
696 if (value) {
697 ZVAL_DEREF(value);
698 }
699 // 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.
700 switch (thrift_typeID) {
701 case T_STOP:
702 case T_VOID:
703 return;
704 case T_STRUCT: {
705 if (Z_TYPE_P(value) != IS_OBJECT) {
706 throw_tprotocolexception("Attempt to send non-object type as a T_STRUCT", INVALID_DATA);
707 }
708 zval* spec = zend_read_static_property(Z_OBJCE_P(value), "_TSPEC", sizeof("_TSPEC")-1, true);
709 if (spec && Z_TYPE_P(spec) == IS_REFERENCE) {
710 ZVAL_DEREF(spec);
711 }
712 if (!spec || Z_TYPE_P(spec) != IS_ARRAY) {
713 throw_tprotocolexception("Attempt to send non-Thrift object as a T_STRUCT", INVALID_DATA);
714 }
715 binary_serialize_spec(value, transport, Z_ARRVAL_P(spec));
716 } return;
717 case T_BOOL:
718 if (!zval_is_bool(value)) convert_to_boolean(value);
719 transport.writeI8(Z_TYPE_INFO_P(value) == IS_TRUE ? 1 : 0);
720 return;
721 case T_BYTE:
722 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
723 transport.writeI8(Z_LVAL_P(value));
724 return;
725 case T_I16:
726 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
727 transport.writeI16(Z_LVAL_P(value));
728 return;
729 case T_I32:
730 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
731 transport.writeI32(Z_LVAL_P(value));
732 return;
733 case T_I64:
734 case T_U64: {
735 int64_t l_data;
736 #if defined(_LP64) || defined(_WIN64)
737 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
738 l_data = Z_LVAL_P(value);
739 #else
740 if (Z_TYPE_P(value) != IS_DOUBLE) convert_to_double(value);
741 l_data = (int64_t)Z_DVAL_P(value);
742 #endif
743 transport.writeI64(l_data);
744 } return;
745 case T_DOUBLE: {
746 union {
747 int64_t c;
748 double d;
749 } a;
750 if (Z_TYPE_P(value) != IS_DOUBLE) convert_to_double(value);
751 a.d = Z_DVAL_P(value);
752 transport.writeI64(a.c);
753 } return;
754 case T_UTF8:
755 case T_UTF16:
756 case T_STRING:
757 if (Z_TYPE_P(value) != IS_STRING) convert_to_string(value);
758 transport.writeString(Z_STRVAL_P(value), Z_STRLEN_P(value));
759 return;
760 case T_MAP: {
761 if (Z_TYPE_P(value) != IS_ARRAY) convert_to_array(value);
762 if (Z_TYPE_P(value) != IS_ARRAY) {
763 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_MAP)", INVALID_DATA);
764 }
765 HashTable* ht = Z_ARRVAL_P(value);
766 zval* val_ptr;
767
768 val_ptr = zend_hash_str_find(fieldspec, "ktype", sizeof("ktype")-1);
769 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
770 uint8_t keytype = Z_LVAL_P(val_ptr);
771 transport.writeI8(keytype);
772 val_ptr = zend_hash_str_find(fieldspec, "vtype", sizeof("vtype")-1);
773 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
774 uint8_t valtype = Z_LVAL_P(val_ptr);
775 transport.writeI8(valtype);
776
777 val_ptr = zend_hash_str_find(fieldspec, "val", sizeof("val")-1);
778 HashTable* valspec = Z_ARRVAL_P(val_ptr);
779 HashTable* keyspec = Z_ARRVAL_P(zend_hash_str_find(fieldspec, "key", sizeof("key")-1));
780
781 transport.writeI32(zend_hash_num_elements(ht));
782 HashPosition key_ptr;
783 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
784 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
785 zend_hash_move_forward_ex(ht, &key_ptr)) {
786 binary_serialize_hashtable_key(keytype, transport, ht, key_ptr, keyspec);
787 binary_serialize(valtype, transport, val_ptr, valspec);
788 }
789 } return;
790 case T_LIST: {
791 if (Z_TYPE_P(value) != IS_ARRAY) convert_to_array(value);
792 if (Z_TYPE_P(value) != IS_ARRAY) {
793 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_LIST)", INVALID_DATA);
794 }
795 HashTable* ht = Z_ARRVAL_P(value);
796 zval* val_ptr;
797
798 val_ptr = zend_hash_str_find(fieldspec, "etype", sizeof("etype")-1);
799 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
800 uint8_t valtype = Z_LVAL_P(val_ptr);
801 transport.writeI8(valtype);
802
803 val_ptr = zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1);
804 HashTable* valspec = Z_ARRVAL_P(val_ptr);
805
806 transport.writeI32(zend_hash_num_elements(ht));
807 HashPosition key_ptr;
808 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
809 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
810 zend_hash_move_forward_ex(ht, &key_ptr)) {
811 binary_serialize(valtype, transport, val_ptr, valspec);
812 }
813 } return;
814 case T_SET: {
815 if (Z_TYPE_P(value) != IS_ARRAY) convert_to_array(value);
816 if (Z_TYPE_P(value) != IS_ARRAY) {
817 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_SET)", INVALID_DATA);
818 }
819 HashTable* ht = Z_ARRVAL_P(value);
820 zval* val_ptr;
821
822 val_ptr = zend_hash_str_find(fieldspec, "etype", sizeof("etype")-1);
823 HashTable* spec = Z_ARRVAL_P(zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1));
824 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
825 uint8_t keytype = Z_LVAL_P(val_ptr);
826 transport.writeI8(keytype);
827
828 transport.writeI32(zend_hash_num_elements(ht));
829 HashPosition key_ptr;
830 if(ttype_is_scalar(keytype)){
831 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
832 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
833 zend_hash_move_forward_ex(ht, &key_ptr)) {
834 binary_serialize_hashtable_key(keytype, transport, ht, key_ptr, spec);
835 }
836 } else {
837 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
838 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
839 zend_hash_move_forward_ex(ht, &key_ptr)) {
840 binary_serialize(keytype, transport, val_ptr, spec);
841 }
842 }
843 } return;
844 };
845
846 char errbuf[128];
847 snprintf(errbuf, 128, "Unknown thrift typeID %d", thrift_typeID);
848 throw_tprotocolexception(errbuf, INVALID_DATA);
849 }
850
851 static
protocol_writeMessageBegin(zval * transport,zend_string * method_name,int32_t msgtype,int32_t seqID)852 void protocol_writeMessageBegin(zval* transport, zend_string* method_name, int32_t msgtype, int32_t seqID) {
853 zval args[3];
854 zval ret;
855 zval writeMessagefn;
856
857 ZVAL_STR_COPY(&args[0], method_name);
858 ZVAL_LONG(&args[1], msgtype);
859 ZVAL_LONG(&args[2], seqID);
860 ZVAL_NULL(&ret);
861 ZVAL_STRING(&writeMessagefn, "writeMessageBegin");
862
863 call_user_function(EG(function_table), transport, &writeMessagefn, &ret, 3, args);
864
865 zval_dtor(&writeMessagefn);
866 zval_dtor(&args[2]); zval_dtor(&args[1]); zval_dtor(&args[0]);
867 zval_dtor(&ret);
868 if (EG(exception)) {
869 zend_object *ex = EG(exception);
870 EG(exception) = nullptr;
871 throw PHPExceptionWrapper(ex);
872 }
873 }
874
875 static inline
ttype_is_int(int8_t t)876 bool ttype_is_int(int8_t t) {
877 return ((t == T_BYTE) || ((t >= T_I16) && (t <= T_I64)));
878 }
879 static inline
ttype_is_scalar(int8_t t)880 bool ttype_is_scalar(int8_t t) {
881 return !((t == T_STRUCT) || ( t== T_MAP) || (t == T_SET) || (t == T_LIST));
882 }
883
884 static inline
ttypes_are_compatible(int8_t t1,int8_t t2)885 bool ttypes_are_compatible(int8_t t1, int8_t t2) {
886 // Integer types of different widths are considered compatible;
887 // otherwise the typeID must match.
888 return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2)));
889 }
890
891 //is used to validate objects before serialization and after deserialization. For now, only required fields are validated.
892 static
validate_thrift_object(zval * object)893 void validate_thrift_object(zval* object) {
894 zend_class_entry* object_class_entry = Z_OBJCE_P(object);
895 zval* is_validate = zend_read_static_property(object_class_entry, "isValidate", sizeof("isValidate")-1, true);
896 if (is_validate) {
897 ZVAL_DEREF(is_validate);
898 }
899 zval* spec = zend_read_static_property(object_class_entry, "_TSPEC", sizeof("_TSPEC")-1, true);
900 if (spec) {
901 ZVAL_DEREF(spec);
902 }
903 HashPosition key_ptr;
904 zval* val_ptr;
905
906 if (is_validate && Z_TYPE_INFO_P(is_validate) == IS_TRUE) {
907 for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(spec), &key_ptr);
908 (val_ptr = zend_hash_get_current_data_ex(Z_ARRVAL_P(spec), &key_ptr)) != nullptr;
909 zend_hash_move_forward_ex(Z_ARRVAL_P(spec), &key_ptr)) {
910
911 zend_ulong fieldno;
912 if (zend_hash_get_current_key_ex(Z_ARRVAL_P(spec), nullptr, &fieldno, &key_ptr) != HASH_KEY_IS_LONG) {
913 throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA);
914 return;
915 }
916 HashTable* fieldspec = Z_ARRVAL_P(val_ptr);
917
918 // field name
919 zval* zvarname = zend_hash_str_find(fieldspec, "var", sizeof("var")-1);
920 char* varname = Z_STRVAL_P(zvarname);
921
922 zval* is_required = zend_hash_str_find(fieldspec, "isRequired", sizeof("isRequired")-1);
923 zval rv;
924 zval* prop = zend_read_property(object_class_entry, Z4_OBJ_P(object), varname, strlen(varname), false, &rv);
925
926 if (Z_TYPE_INFO_P(is_required) == IS_TRUE && Z_TYPE_P(prop) == IS_NULL) {
927 char errbuf[128];
928 snprintf(errbuf, 128, "Required field %s.%s is unset!", ZSTR_VAL(object_class_entry->name), varname);
929 throw_tprotocolexception(errbuf, INVALID_DATA);
930 }
931 }
932 }
933 }
934
935 static
binary_deserialize_spec(zval * zthis,PHPInputTransport & transport,HashTable * spec)936 void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec) {
937 // SET and LIST have 'elem' => array('type', [optional] 'class')
938 // MAP has 'val' => array('type', [optiona] 'class')
939 zend_class_entry* ce = Z_OBJCE_P(zthis);
940 while (true) {
941 int8_t ttype = transport.readI8();
942 if (ttype == T_STOP) {
943 validate_thrift_object(zthis);
944 return;
945 }
946
947 int16_t fieldno = transport.readI16();
948 zval* val_ptr = zend_hash_index_find(spec, fieldno);
949 if (val_ptr != nullptr) {
950 HashTable* fieldspec = Z_ARRVAL_P(val_ptr);
951 // pull the field name
952 val_ptr = zend_hash_str_find(fieldspec, "var", sizeof("var")-1);
953 char* varname = Z_STRVAL_P(val_ptr);
954
955 // and the type
956 val_ptr = zend_hash_str_find(fieldspec, "type", sizeof("type")-1);
957 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
958 int8_t expected_ttype = Z_LVAL_P(val_ptr);
959
960 if (ttypes_are_compatible(ttype, expected_ttype)) {
961 zval rv;
962 ZVAL_UNDEF(&rv);
963
964 binary_deserialize(ttype, transport, &rv, fieldspec);
965 zend_update_property(ce, Z4_OBJ_P(zthis), varname, strlen(varname), &rv);
966
967 zval_ptr_dtor(&rv);
968 } else {
969 skip_element(ttype, transport);
970 }
971 } else {
972 skip_element(ttype, transport);
973 }
974 }
975 }
976
977 static
binary_serialize_spec(zval * zthis,PHPOutputTransport & transport,HashTable * spec)978 void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec) {
979
980 validate_thrift_object(zthis);
981
982 HashPosition key_ptr;
983 zval* val_ptr;
984
985 for (zend_hash_internal_pointer_reset_ex(spec, &key_ptr);
986 (val_ptr = zend_hash_get_current_data_ex(spec, &key_ptr)) != nullptr;
987 zend_hash_move_forward_ex(spec, &key_ptr)) {
988
989 zend_ulong fieldno;
990 if (zend_hash_get_current_key_ex(spec, nullptr, &fieldno, &key_ptr) != HASH_KEY_IS_LONG) {
991 throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA);
992 return;
993 }
994 HashTable* fieldspec = Z_ARRVAL_P(val_ptr);
995
996 // field name
997 val_ptr = zend_hash_str_find(fieldspec, "var", sizeof("var")-1);
998 char* varname = Z_STRVAL_P(val_ptr);
999
1000 // thrift type
1001 val_ptr = zend_hash_str_find(fieldspec, "type", sizeof("type")-1);
1002 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
1003 int8_t ttype = Z_LVAL_P(val_ptr);
1004
1005 zval rv;
1006 zval* prop = zend_read_property(Z_OBJCE_P(zthis), Z4_OBJ_P(zthis), varname, strlen(varname), false, &rv);
1007
1008 if (Z_TYPE_P(prop) == IS_REFERENCE){
1009 ZVAL_DEREF(prop);
1010 }
1011 if (Z_TYPE_P(prop) != IS_NULL) {
1012 transport.writeI8(ttype);
1013 transport.writeI16(fieldno);
1014 binary_serialize(ttype, transport, prop, fieldspec);
1015 }
1016 }
1017 transport.writeI8(T_STOP); // struct end
1018 }
1019
1020 // 6 params: $transport $method_name $ttype $request_struct $seqID $strict_write
PHP_FUNCTION(thrift_protocol_write_binary)1021 PHP_FUNCTION(thrift_protocol_write_binary) {
1022 zval *protocol;
1023 zval *request_struct;
1024 zend_string *method_name;
1025 long msgtype, seqID;
1026 zend_bool strict_write;
1027
1028 if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "oSlolb",
1029 &protocol, &method_name, &msgtype,
1030 &request_struct, &seqID, &strict_write) == FAILURE) {
1031 return;
1032 }
1033
1034 try {
1035 zval* spec = zend_read_static_property(Z_OBJCE_P(request_struct), "_TSPEC", sizeof("_TSPEC")-1, true);
1036 if (spec) {
1037 ZVAL_DEREF(spec);
1038 }
1039
1040 if (!spec || Z_TYPE_P(spec) != IS_ARRAY) {
1041 throw_tprotocolexception("Attempt serialize from non-Thrift object", INVALID_DATA);
1042 }
1043
1044 PHPOutputTransport transport(protocol);
1045 protocol_writeMessageBegin(protocol, method_name, (int32_t) msgtype, (int32_t) seqID);
1046 binary_serialize_spec(request_struct, transport, Z_ARRVAL_P(spec));
1047 transport.flush();
1048
1049 } catch (const PHPExceptionWrapper& ex) {
1050 // ex will be destructed, so copy to a zval that zend_throw_exception_object can take ownership of
1051 zval myex;
1052 ZVAL_COPY(&myex, ex);
1053 zend_throw_exception_object(&myex);
1054 RETURN_NULL();
1055 } catch (const std::exception& ex) {
1056 throw_zend_exception_from_std_exception(ex);
1057 RETURN_NULL();
1058 }
1059 }
1060
1061
1062 // 4 params: $transport $response_Typename $strict_read $buffer_size
PHP_FUNCTION(thrift_protocol_read_binary)1063 PHP_FUNCTION(thrift_protocol_read_binary) {
1064 zval *protocol;
1065 zend_string *obj_typename;
1066 zend_bool strict_read;
1067 size_t buffer_size = 8192;
1068
1069 if (zend_parse_parameters(ZEND_NUM_ARGS(), "oSb|l", &protocol, &obj_typename, &strict_read, &buffer_size) == FAILURE) {
1070 return;
1071 }
1072
1073 try {
1074 PHPInputTransport transport(protocol, buffer_size);
1075 int8_t messageType = 0;
1076 int32_t sz = transport.readI32();
1077
1078 if (sz < 0) {
1079 // Check for correct version number
1080 int32_t version = sz & VERSION_MASK;
1081 if (version != VERSION_1) {
1082 throw_tprotocolexception("Bad version identifier", BAD_VERSION);
1083 }
1084 messageType = (sz & 0x000000ff);
1085 int32_t namelen = transport.readI32();
1086 // skip the name string and the sequence ID, we don't care about those
1087 transport.skip(namelen + 4);
1088 } else {
1089 if (strict_read) {
1090 throw_tprotocolexception("No version identifier... old protocol client in strict mode?", BAD_VERSION);
1091 } else {
1092 // Handle pre-versioned input
1093 transport.skip(sz); // skip string body
1094 messageType = transport.readI8();
1095 transport.skip(4); // skip sequence number
1096 }
1097 }
1098
1099 if (messageType == T_EXCEPTION) {
1100 zval ex;
1101 createObject("\\Thrift\\Exception\\TApplicationException", &ex);
1102 zval* spec = zend_read_static_property(Z_OBJCE(ex), "_TSPEC", sizeof("_TPSEC")-1, false);
1103 ZVAL_DEREF(spec);
1104 if (EG(exception)) {
1105 zend_object *ex = EG(exception);
1106 EG(exception) = nullptr;
1107 throw PHPExceptionWrapper(ex);
1108 }
1109 binary_deserialize_spec(&ex, transport, Z_ARRVAL_P(spec));
1110 throw PHPExceptionWrapper(&ex);
1111 }
1112
1113 createObject(ZSTR_VAL(obj_typename), return_value);
1114 zval* spec = zend_read_static_property(Z_OBJCE_P(return_value), "_TSPEC", sizeof("_TSPEC")-1, true);
1115 if (spec) {
1116 ZVAL_DEREF(spec);
1117 }
1118 if (!spec || Z_TYPE_P(spec) != IS_ARRAY) {
1119 throw_tprotocolexception("Attempt deserialize to non-Thrift object", INVALID_DATA);
1120 }
1121 binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
1122 } catch (const PHPExceptionWrapper& ex) {
1123 // ex will be destructed, so copy to a zval that zend_throw_exception_object can ownership of
1124 zval myex;
1125 ZVAL_COPY(&myex, ex);
1126 zval_dtor(return_value);
1127 zend_throw_exception_object(&myex);
1128 RETURN_NULL();
1129 } catch (const std::exception& ex) {
1130 throw_zend_exception_from_std_exception(ex);
1131 RETURN_NULL();
1132 }
1133 }
1134
1135 // 4 params: $transport $response_Typename $strict_read $buffer_size
PHP_FUNCTION(thrift_protocol_read_binary_after_message_begin)1136 PHP_FUNCTION(thrift_protocol_read_binary_after_message_begin) {
1137 zval *protocol;
1138 zend_string *obj_typename;
1139 zend_bool strict_read;
1140 size_t buffer_size = 8192;
1141
1142 if (zend_parse_parameters(ZEND_NUM_ARGS(), "oSb|l", &protocol, &obj_typename, &strict_read, &buffer_size) == FAILURE) {
1143 return;
1144 }
1145
1146 try {
1147 PHPInputTransport transport(protocol, buffer_size);
1148
1149 createObject(ZSTR_VAL(obj_typename), return_value);
1150 zval* spec = zend_read_static_property(Z_OBJCE_P(return_value), "_TSPEC", sizeof("_TSPEC")-1, false);
1151 ZVAL_DEREF(spec);
1152 binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
1153 } catch (const PHPExceptionWrapper& ex) {
1154 // ex will be destructed, so copy to a zval that zend_throw_exception_object can take ownership of
1155 zval myex;
1156 ZVAL_COPY(&myex, ex);
1157 zend_throw_exception_object(&myex);
1158 RETURN_NULL();
1159 } catch (const std::exception& ex) {
1160 throw_zend_exception_from_std_exception(ex);
1161 RETURN_NULL();
1162 }
1163 }
1164
1165 #endif /* PHP_VERSION_ID >= 70000 */
1166