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