Project

General

Profile

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

    
20
#ifdef HAVE_CONFIG_H
21
#include "config.h"
22
#endif
23

    
24
#include <sys/types.h>
25
#include <netinet/in.h>
26
#include <unistd.h>
27
#include <endian.h>
28
#include <byteswap.h>
29
#include <stdexcept>
30

    
31
#if __BYTE_ORDER == __LITTLE_ENDIAN
32
#define htonll(x) bswap_64(x)
33
#define ntohll(x) bswap_64(x)
34
#else
35
#define htonll(x) x
36
#define ntohll(x) x
37
#endif
38

    
39
enum TType {
40
  T_STOP       = 0,
41
  T_VOID       = 1,
42
  T_BOOL       = 2,
43
  T_BYTE       = 3,
44
  T_I08        = 3,
45
  T_I16        = 6,
46
  T_I32        = 8,
47
  T_U64        = 9,
48
  T_I64        = 10,
49
  T_DOUBLE     = 4,
50
  T_STRING     = 11,
51
  T_UTF7       = 11,
52
  T_STRUCT     = 12,
53
  T_MAP        = 13,
54
  T_SET        = 14,
55
  T_LIST       = 15,
56
  T_UTF8       = 16,
57
  T_UTF16      = 17
58
};
59

    
60
const int32_t VERSION_MASK = 0xffff0000;
61
const int32_t VERSION_1 = 0x80010000;
62
const int8_t T_CALL = 1;
63
const int8_t T_REPLY = 2;
64
const int8_t T_EXCEPTION = 3;
65
// tprotocolexception
66
const int INVALID_DATA = 1;
67
const int BAD_VERSION = 4;
68

    
69
#include "php.h"
70
#include "zend_interfaces.h"
71
#include "zend_exceptions.h"
72
#include "php_thrift_protocol.h"
73

    
74
static function_entry thrift_protocol_functions[] = {
75
  PHP_FE(thrift_protocol_write_binary, NULL)
76
  PHP_FE(thrift_protocol_read_binary, NULL)
77
  {NULL, NULL, NULL}
78
} ;
79

    
80
zend_module_entry thrift_protocol_module_entry = {
81
  STANDARD_MODULE_HEADER,
82
  "thrift_protocol",
83
  thrift_protocol_functions,
84
  NULL,
85
  NULL,
86
  NULL,
87
  NULL,
88
  NULL,
89
  "1.0",
90
  STANDARD_MODULE_PROPERTIES
91
};
92

    
93
#ifdef COMPILE_DL_THRIFT_PROTOCOL
94
ZEND_GET_MODULE(thrift_protocol)
95
#endif
96

    
97
class PHPExceptionWrapper : public std::exception {
98
public:
99
  PHPExceptionWrapper(zval* _ex) throw() : ex(_ex) {
100
    snprintf(_what, 40, "PHP exception zval=%p", ex);
101
  }
102
  const char* what() const throw() { return _what; }
103
  ~PHPExceptionWrapper() throw() {}
104
  operator zval*() const throw() { return const_cast<zval*>(ex); } // Zend API doesn't do 'const'...
105
protected:
106
  zval* ex;
107
  char _what[40];
108
} ;
109

    
110
class PHPTransport {
111
public:
112
  zval* protocol() { return p; }
113
  zval* transport() { return t; }
114
protected:
115
  PHPTransport() {}
116

    
117
  void construct_with_zval(zval* _p, size_t _buffer_size) {
118
    buffer = reinterpret_cast<char*>(emalloc(_buffer_size));
119
    buffer_ptr = buffer;
120
    buffer_used = 0;
121
    buffer_size = _buffer_size;
122
    p = _p;
123

    
124
    // Get the transport for the passed protocol
125
    zval gettransport;
126
    ZVAL_STRING(&gettransport, "getTransport", 0);
127
    MAKE_STD_ZVAL(t);
128
    ZVAL_NULL(t);
129
    TSRMLS_FETCH();
130
    call_user_function(EG(function_table), &p, &gettransport, t, 0, NULL TSRMLS_CC);
131
  }
132
  ~PHPTransport() {
133
    efree(buffer);
134
    zval_ptr_dtor(&t);
135
  }
136

    
137
  char* buffer;
138
  char* buffer_ptr;
139
  size_t buffer_used;
140
  size_t buffer_size;
141

    
142
  zval* p;
143
  zval* t;
144
};
145

    
146

    
147
class PHPOutputTransport : public PHPTransport {
148
public:
149
  PHPOutputTransport(zval* _p, size_t _buffer_size = 8192) {
150
    construct_with_zval(_p, _buffer_size);
151
  }
152

    
153
  ~PHPOutputTransport() {
154
    flush();
155
  }
156

    
157
  void write(const char* data, size_t len) {
158
    if ((len + buffer_used) > buffer_size) {
159
      flush();
160
    }
161
    if (len > buffer_size) {
162
      directWrite(data, len);
163
    } else {
164
      memcpy(buffer_ptr, data, len);
165
      buffer_used += len;
166
      buffer_ptr += len;
167
    }
168
  }
169

    
170
  void writeI64(int64_t i) {
171
    i = htonll(i);
172
    write((const char*)&i, 8);
173
  }
174

    
175
  void writeU32(uint32_t i) {
176
    i = htonl(i);
177
    write((const char*)&i, 4);
178
  }
179

    
180
  void writeI32(int32_t i) {
181
    i = htonl(i);
182
    write((const char*)&i, 4);
183
  }
184

    
185
  void writeI16(int16_t i) {
186
    i = htons(i);
187
    write((const char*)&i, 2);
188
  }
189

    
190
  void writeI8(int8_t i) {
191
    write((const char*)&i, 1);
192
  }
193

    
194
  void writeString(const char* str, size_t len) {
195
    writeU32(len);
196
    write(str, len);
197
  }
198

    
199
  void flush() {
200
    if (buffer_used) {
201
      directWrite(buffer, buffer_used);
202
      buffer_ptr = buffer;
203
      buffer_used = 0;
204
    }
205
    directFlush();
206
  }
207

    
208
protected:
209
  void directFlush() {
210
    zval ret;
211
    ZVAL_NULL(&ret);
212
    zval flushfn;
213
    ZVAL_STRING(&flushfn, "flush", 0);
214
    TSRMLS_FETCH();
215
    call_user_function(EG(function_table), &t, &flushfn, &ret, 0, NULL TSRMLS_CC);
216
    zval_dtor(&ret);
217
  }
218
  void directWrite(const char* data, size_t len) {
219
    zval writefn;
220
    ZVAL_STRING(&writefn, "write", 0);
221
    char* newbuf = (char*)emalloc(len + 1);
222
    memcpy(newbuf, data, len);
223
    newbuf[len] = '\0';
224
    zval *args[1];
225
    MAKE_STD_ZVAL(args[0]);
226
    ZVAL_STRINGL(args[0], newbuf, len, 0);
227
    TSRMLS_FETCH();
228
    zval ret;
229
    ZVAL_NULL(&ret);
230
    call_user_function(EG(function_table), &t, &writefn, &ret, 1, args TSRMLS_CC);
231
    zval_ptr_dtor(args);
232
    zval_dtor(&ret);
233
    if (EG(exception)) {
234
      zval* ex = EG(exception);
235
      EG(exception) = NULL;
236
      throw PHPExceptionWrapper(ex);
237
    }
238
  }
239
};
240

    
241
class PHPInputTransport : public PHPTransport {
242
public:
243
  PHPInputTransport(zval* _p, size_t _buffer_size = 8192) {
244
    construct_with_zval(_p, _buffer_size);
245
  }
246

    
247
  ~PHPInputTransport() {
248
    put_back();
249
  }
250

    
251
  void put_back() {
252
    if (buffer_used) {
253
      zval putbackfn;
254
      ZVAL_STRING(&putbackfn, "putBack", 0);
255

    
256
      char* newbuf = (char*)emalloc(buffer_used + 1);
257
      memcpy(newbuf, buffer_ptr, buffer_used);
258
      newbuf[buffer_used] = '\0';
259

    
260
      zval *args[1];
261
      MAKE_STD_ZVAL(args[0]);
262
      ZVAL_STRINGL(args[0], newbuf, buffer_used, 0);
263

    
264
      TSRMLS_FETCH();
265

    
266
      zval ret;
267
      ZVAL_NULL(&ret);
268
      call_user_function(EG(function_table), &t, &putbackfn, &ret, 1, args TSRMLS_CC);
269
      zval_ptr_dtor(args);
270
      zval_dtor(&ret);
271
    }
272
    buffer_used = 0;
273
    buffer_ptr = buffer;
274
  }
275

    
276
  void skip(size_t len) {
277
    while (len) {
278
      size_t chunk_size = MIN(len, buffer_used);
279
      if (chunk_size) {
280
        buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size;
281
        buffer_used -= chunk_size;
282
        len -= chunk_size;
283
      }
284
      if (! len) break;
285
      refill();
286
    }
287
  }
288

    
289
  void readBytes(void* buf, size_t len) {
290
    while (len) {
291
      size_t chunk_size = MIN(len, buffer_used);
292
      if (chunk_size) {
293
        memcpy(buf, buffer_ptr, chunk_size);
294
        buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size;
295
        buffer_used -= chunk_size;
296
        buf = reinterpret_cast<char*>(buf) + chunk_size;
297
        len -= chunk_size;
298
      }
299
      if (! len) break;
300
      refill();
301
    }
302
  }
303

    
304
  int8_t readI8() {
305
    int8_t c;
306
    readBytes(&c, 1);
307
    return c;
308
  }
309

    
310
  int16_t readI16() {
311
    int16_t c;
312
    readBytes(&c, 2);
313
    return (int16_t)ntohs(c);
314
  }
315

    
316
  uint32_t readU32() {
317
    uint32_t c;
318
    readBytes(&c, 4);
319
    return (uint32_t)ntohl(c);
320
  }
321

    
322
  int32_t readI32() {
323
    int32_t c;
324
    readBytes(&c, 4);
325
    return (int32_t)ntohl(c);
326
  }
327

    
328
protected:
329
  void refill() {
330
    assert(buffer_used == 0);
331
    zval retval;
332
    ZVAL_NULL(&retval);
333

    
334
    zval *args[1];
335
    MAKE_STD_ZVAL(args[0]);
336
    ZVAL_LONG(args[0], buffer_size);
337

    
338
    TSRMLS_FETCH();
339

    
340
    zval funcname;
341
    ZVAL_STRING(&funcname, "read", 0);
342

    
343
    call_user_function(EG(function_table), &t, &funcname, &retval, 1, args TSRMLS_CC);
344
    zval_ptr_dtor(args);
345

    
346
    if (EG(exception)) {
347
      zval_dtor(&retval);
348
      zval* ex = EG(exception);
349
      EG(exception) = NULL;
350
      throw PHPExceptionWrapper(ex);
351
    }
352

    
353
    buffer_used = Z_STRLEN(retval);
354
    memcpy(buffer, Z_STRVAL(retval), buffer_used);
355
    zval_dtor(&retval);
356

    
357
    buffer_ptr = buffer;
358
  }
359

    
360
};
361

    
362
void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec);
363
void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec);
364
void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval** value, HashTable* fieldspec);
365
void skip_element(long thrift_typeID, PHPInputTransport& transport);
366

    
367
// Create a PHP object given a typename and call the ctor, optionally passing up to 2 arguments
368
void createObject(char* obj_typename, zval* return_value, int nargs = 0, zval* arg1 = NULL, zval* arg2 = NULL) {
369
  TSRMLS_FETCH();
370
  size_t obj_typename_len = strlen(obj_typename);
371
  zend_class_entry* ce = zend_fetch_class(obj_typename, obj_typename_len, ZEND_FETCH_CLASS_DEFAULT TSRMLS_CC);
372
  if (! ce) {
373
    php_error_docref(NULL TSRMLS_CC, E_ERROR, "Class %s does not exist", obj_typename);
374
    RETURN_NULL();
375
  }
376

    
377
  object_and_properties_init(return_value, ce, NULL);
378
  zend_function* constructor = zend_std_get_constructor(return_value TSRMLS_CC);
379
  zval* ctor_rv = NULL;
380
  zend_call_method(&return_value, ce, &constructor, NULL, 0, &ctor_rv, nargs, arg1, arg2 TSRMLS_CC);
381
  zval_ptr_dtor(&ctor_rv);
382
}
383

    
384
void throw_tprotocolexception(char* what, long errorcode) {
385
  TSRMLS_FETCH();
386

    
387
  zval *zwhat, *zerrorcode;
388
  MAKE_STD_ZVAL(zwhat);
389
  MAKE_STD_ZVAL(zerrorcode);
390

    
391
  ZVAL_STRING(zwhat, what, 1);
392
  ZVAL_LONG(zerrorcode, errorcode);
393

    
394
  zval* ex;
395
  MAKE_STD_ZVAL(ex);
396
  createObject("TProtocolException", ex, 2, zwhat, zerrorcode);
397
  zval_ptr_dtor(&zwhat);
398
  zval_ptr_dtor(&zerrorcode);
399
  throw PHPExceptionWrapper(ex);
400
}
401

    
402
// Sets EG(exception), call this and then RETURN_NULL();
403
void throw_zend_exception_from_std_exception(const std::exception& ex) {
404
  zend_throw_exception(zend_exception_get_default(TSRMLS_CC), const_cast<char*>(ex.what()), 0 TSRMLS_CC);
405
}
406

    
407

    
408
void binary_deserialize(int8_t thrift_typeID, PHPInputTransport& transport, zval* return_value, HashTable* fieldspec) {
409
  zval** val_ptr;
410
  Z_TYPE_P(return_value) = IS_NULL; // just in case
411

    
412
  switch (thrift_typeID) {
413
    case T_STOP:
414
    case T_VOID:
415
      RETURN_NULL();
416
      return;
417
    case T_STRUCT: {
418
      if (zend_hash_find(fieldspec, "class", 6, (void**)&val_ptr) != SUCCESS) {
419
        throw_tprotocolexception("no class type in spec", INVALID_DATA);
420
        skip_element(T_STRUCT, transport);
421
        RETURN_NULL();
422
      }
423
      char* structType = Z_STRVAL_PP(val_ptr);
424
      createObject(structType, return_value);
425
      if (Z_TYPE_P(return_value) == IS_NULL) {
426
        // unable to create class entry
427
        skip_element(T_STRUCT, transport);
428
        RETURN_NULL();
429
      }
430
      TSRMLS_FETCH();
431
      zval* spec = zend_read_static_property(zend_get_class_entry(return_value TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC);
432
      if (Z_TYPE_P(spec) != IS_ARRAY) {
433
        char errbuf[128];
434
        snprintf(errbuf, 128, "spec for %s is wrong type: %d\n", structType, Z_TYPE_P(spec));
435
        throw_tprotocolexception(errbuf, INVALID_DATA);
436
        RETURN_NULL();
437
      }
438
      binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
439
      return;
440
    } break;
441
    case T_BOOL: {
442
      uint8_t c;
443
      transport.readBytes(&c, 1);
444
      RETURN_BOOL(c != 0);
445
    }
446
  //case T_I08: // same numeric value as T_BYTE
447
    case T_BYTE: {
448
      uint8_t c;
449
      transport.readBytes(&c, 1);
450
      RETURN_LONG((int8_t)c);
451
    }
452
    case T_I16: {
453
      uint16_t c;
454
      transport.readBytes(&c, 2);
455
      RETURN_LONG((int16_t)ntohs(c));
456
    }
457
    case T_I32: {
458
      uint32_t c;
459
      transport.readBytes(&c, 4);
460
      RETURN_LONG((int32_t)ntohl(c));
461
    }
462
    case T_U64:
463
    case T_I64: {
464
      uint64_t c;
465
      transport.readBytes(&c, 8);
466
      RETURN_LONG((int64_t)ntohll(c));
467
    }
468
    case T_DOUBLE: {
469
      union {
470
        uint64_t c;
471
        double d;
472
      } a;
473
      transport.readBytes(&(a.c), 8);
474
      a.c = ntohll(a.c);
475
      RETURN_DOUBLE(a.d);
476
    }
477
    //case T_UTF7: // aliases T_STRING
478
    case T_UTF8:
479
    case T_UTF16:
480
    case T_STRING: {
481
      uint32_t size = transport.readU32();
482
      if (size) {
483
        char* strbuf = (char*) emalloc(size + 1);
484
        transport.readBytes(strbuf, size);
485
        strbuf[size] = '\0';
486
        ZVAL_STRINGL(return_value, strbuf, size, 0);
487
      } else {
488
        ZVAL_EMPTY_STRING(return_value);
489
      }
490
      return;
491
    }
492
    case T_MAP: { // array of key -> value
493
      uint8_t types[2];
494
      transport.readBytes(types, 2);
495
      uint32_t size = transport.readU32();
496
      array_init(return_value);
497

    
498
      zend_hash_find(fieldspec, "key", 4, (void**)&val_ptr);
499
      HashTable* keyspec = Z_ARRVAL_PP(val_ptr);
500
      zend_hash_find(fieldspec, "val", 4, (void**)&val_ptr);
501
      HashTable* valspec = Z_ARRVAL_PP(val_ptr);
502

    
503
      for (uint32_t s = 0; s < size; ++s) {
504
        zval *value;
505
        MAKE_STD_ZVAL(value);
506

    
507
        zval* key;
508
        MAKE_STD_ZVAL(key);
509

    
510
        binary_deserialize(types[0], transport, key, keyspec);
511
        binary_deserialize(types[1], transport, value, valspec);
512
        if (Z_TYPE_P(key) == IS_LONG) {
513
          zend_hash_index_update(return_value->value.ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL);
514
        }
515
        else {
516
          if (Z_TYPE_P(key) != IS_STRING) convert_to_string(key);
517
          zend_hash_update(return_value->value.ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL);
518
        }
519
        zval_ptr_dtor(&key);
520
      }
521
      return; // return_value already populated
522
    }
523
    case T_LIST: { // array with autogenerated numeric keys
524
      int8_t type = transport.readI8();
525
      uint32_t size = transport.readU32();
526
      zend_hash_find(fieldspec, "elem", 5, (void**)&val_ptr);
527
      HashTable* elemspec = Z_ARRVAL_PP(val_ptr);
528

    
529
      array_init(return_value);
530
      for (uint32_t s = 0; s < size; ++s) {
531
        zval *value;
532
        MAKE_STD_ZVAL(value);
533
        binary_deserialize(type, transport, value, elemspec);
534
        zend_hash_next_index_insert(return_value->value.ht, &value, sizeof(zval *), NULL);
535
      }
536
      return;
537
    }
538
    case T_SET: { // array of key -> TRUE
539
      uint8_t type;
540
      uint32_t size;
541
      transport.readBytes(&type, 1);
542
      transport.readBytes(&size, 4);
543
      size = ntohl(size);
544
      zend_hash_find(fieldspec, "elem", 5, (void**)&val_ptr);
545
      HashTable* elemspec = Z_ARRVAL_PP(val_ptr);
546

    
547
      array_init(return_value);
548

    
549
      for (uint32_t s = 0; s < size; ++s) {
550
        zval* key;
551
        zval* value;
552
        MAKE_STD_ZVAL(key);
553
        MAKE_STD_ZVAL(value);
554
        ZVAL_TRUE(value);
555

    
556
        binary_deserialize(type, transport, key, elemspec);
557

    
558
        if (Z_TYPE_P(key) == IS_LONG) {
559
          zend_hash_index_update(return_value->value.ht, Z_LVAL_P(key), &value, sizeof(zval *), NULL);
560
        }
561
        else {
562
          if (Z_TYPE_P(key) != IS_STRING) convert_to_string(key);
563
          zend_hash_update(return_value->value.ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &value, sizeof(zval *), NULL);
564
        }
565
        zval_ptr_dtor(&key);
566
      }
567
      return;
568
    }
569
  };
570

    
571
  char errbuf[128];
572
  sprintf(errbuf, "Unknown thrift typeID %d", thrift_typeID);
573
  throw_tprotocolexception(errbuf, INVALID_DATA);
574
}
575

    
576
void skip_element(long thrift_typeID, PHPInputTransport& transport) {
577
  switch (thrift_typeID) {
578
    case T_STOP:
579
    case T_VOID:
580
      return;
581
    case T_STRUCT:
582
      while (true) {
583
        int8_t ttype = transport.readI8(); // get field type
584
        if (ttype == T_STOP) break;
585
        transport.skip(2); // skip field number, I16
586
        skip_element(ttype, transport); // skip field payload
587
      }
588
      return;
589
    case T_BOOL:
590
    case T_BYTE:
591
      transport.skip(1);
592
      return;
593
    case T_I16:
594
      transport.skip(2);
595
      return;
596
    case T_I32:
597
      transport.skip(4);
598
      return;
599
    case T_U64:
600
    case T_I64:
601
    case T_DOUBLE:
602
      transport.skip(8);
603
      return;
604
    //case T_UTF7: // aliases T_STRING
605
    case T_UTF8:
606
    case T_UTF16:
607
    case T_STRING: {
608
      uint32_t len = transport.readU32();
609
      transport.skip(len);
610
      } return;
611
    case T_MAP: {
612
      int8_t keytype = transport.readI8();
613
      int8_t valtype = transport.readI8();
614
      uint32_t size = transport.readU32();
615
      for (uint32_t i = 0; i < size; ++i) {
616
        skip_element(keytype, transport);
617
        skip_element(valtype, transport);
618
      }
619
    } return;
620
    case T_LIST:
621
    case T_SET: {
622
      int8_t valtype = transport.readI8();
623
      uint32_t size = transport.readU32();
624
      for (uint32_t i = 0; i < size; ++i) {
625
        skip_element(valtype, transport);
626
      }
627
    } return;
628
  };
629

    
630
  char errbuf[128];
631
  sprintf(errbuf, "Unknown thrift typeID %ld", thrift_typeID);
632
  throw_tprotocolexception(errbuf, INVALID_DATA);
633
}
634

    
635
void binary_serialize_hashtable_key(int8_t keytype, PHPOutputTransport& transport, HashTable* ht, HashPosition& ht_pos) {
636
  bool keytype_is_numeric = (!((keytype == T_STRING) || (keytype == T_UTF8) || (keytype == T_UTF16)));
637

    
638
  char* key;
639
  uint key_len;
640
  long index = 0;
641

    
642
  zval* z;
643
  MAKE_STD_ZVAL(z);
644

    
645
  int res = zend_hash_get_current_key_ex(ht, &key, &key_len, (ulong*)&index, 0, &ht_pos);
646
  if (keytype_is_numeric) {
647
    if (res == HASH_KEY_IS_STRING) {
648
      index = strtol(key, NULL, 10);
649
    }
650
    ZVAL_LONG(z, index);
651
  } else {
652
    char buf[64];
653
    if (res == HASH_KEY_IS_STRING) {
654
      key_len -= 1; // skip the null terminator
655
    } else {
656
      sprintf(buf, "%ld", index);
657
      key = buf; key_len = strlen(buf);
658
    }
659
    ZVAL_STRINGL(z, key, key_len, 1);
660
  }
661
  binary_serialize(keytype, transport, &z, NULL);
662
  zval_ptr_dtor(&z);
663
}
664

    
665
inline bool ttype_is_int(int8_t t) {
666
  return ((t == T_BYTE) || ((t >= T_I16)  && (t <= T_I64)));
667
}
668

    
669
inline bool ttypes_are_compatible(int8_t t1, int8_t t2) {
670
  // Integer types of different widths are considered compatible;
671
  // otherwise the typeID must match.
672
  return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2)));
673
}
674

    
675
void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec) {
676
  // SET and LIST have 'elem' => array('type', [optional] 'class')
677
  // MAP has 'val' => array('type', [optiona] 'class')
678
  TSRMLS_FETCH();
679
  zend_class_entry* ce = zend_get_class_entry(zthis TSRMLS_CC);
680
  while (true) {
681
    zval** val_ptr = NULL;
682

    
683
    int8_t ttype = transport.readI8();
684
    if (ttype == T_STOP) return;
685
    int16_t fieldno = transport.readI16();
686
    if (zend_hash_index_find(spec, fieldno, (void**)&val_ptr) == SUCCESS) {
687
      HashTable* fieldspec = Z_ARRVAL_PP(val_ptr);
688
      // pull the field name
689
      // zend hash tables use the null at the end in the length... so strlen(hash key) + 1.
690
      zend_hash_find(fieldspec, "var", 4, (void**)&val_ptr);
691
      char* varname = Z_STRVAL_PP(val_ptr);
692

    
693
      // and the type
694
      zend_hash_find(fieldspec, "type", 5, (void**)&val_ptr);
695
      if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr);
696
      int8_t expected_ttype = Z_LVAL_PP(val_ptr);
697

    
698
      if (ttypes_are_compatible(ttype, expected_ttype)) {
699
        zval* rv = NULL;
700
        MAKE_STD_ZVAL(rv);
701
        binary_deserialize(ttype, transport, rv, fieldspec);
702
        zend_update_property(ce, zthis, varname, strlen(varname), rv TSRMLS_CC);
703
        zval_ptr_dtor(&rv);
704
      } else {
705
        skip_element(ttype, transport);
706
      }
707
    } else {
708
      skip_element(ttype, transport);
709
    }
710
  }
711
}
712

    
713
void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval** value, HashTable* fieldspec) {
714
  // 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.
715
  switch (thrift_typeID) {
716
    case T_STOP:
717
    case T_VOID:
718
      return;
719
    case T_STRUCT: {
720
      TSRMLS_FETCH();
721
      if (Z_TYPE_PP(value) != IS_OBJECT) {
722
        throw_tprotocolexception("Attempt to send non-object type as a T_STRUCT", INVALID_DATA);
723
      }
724
      zval* spec = zend_read_static_property(zend_get_class_entry(*value TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC);
725
      if (Z_TYPE_P(spec) != IS_ARRAY) {
726
        throw_tprotocolexception("Attempt to send non-Thrift object as a T_STRUCT", INVALID_DATA);
727
      }
728
      binary_serialize_spec(*value, transport, Z_ARRVAL_P(spec));
729
    } return;
730
    case T_BOOL:
731
      if (Z_TYPE_PP(value) != IS_BOOL) convert_to_boolean(*value);
732
      transport.writeI8(Z_BVAL_PP(value) ? 1 : 0);
733
      return;
734
    case T_BYTE:
735
      if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value);
736
      transport.writeI8(Z_LVAL_PP(value));
737
      return;
738
    case T_I16:
739
      if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value);
740
      transport.writeI16(Z_LVAL_PP(value));
741
      return;
742
    case T_I32:
743
      if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value);
744
      transport.writeI32(Z_LVAL_PP(value));
745
      return;
746
    case T_I64:
747
    case T_U64:
748
      if (Z_TYPE_PP(value) != IS_LONG) convert_to_long(*value);
749
      transport.writeI64(Z_LVAL_PP(value));
750
      return;
751
    case T_DOUBLE: {
752
      union {
753
        int64_t c;
754
        double d;
755
      } a;
756
      if (Z_TYPE_PP(value) != IS_DOUBLE) convert_to_double(*value);
757
      a.d = Z_DVAL_PP(value);
758
      transport.writeI64(a.c);
759
    } return;
760
    //case T_UTF7:
761
    case T_UTF8:
762
    case T_UTF16:
763
    case T_STRING:
764
      if (Z_TYPE_PP(value) != IS_STRING) convert_to_string(*value);
765
      transport.writeString(Z_STRVAL_PP(value), Z_STRLEN_PP(value));
766
      return;
767
    case T_MAP: {
768
      if (Z_TYPE_PP(value) != IS_ARRAY) convert_to_array(*value);
769
      if (Z_TYPE_PP(value) != IS_ARRAY) {
770
        throw_tprotocolexception("Attempt to send an incompatible type as an array (T_MAP)", INVALID_DATA);
771
      }
772
      HashTable* ht = Z_ARRVAL_PP(value);
773
      zval** val_ptr;
774

    
775
      zend_hash_find(fieldspec, "ktype", 6, (void**)&val_ptr);
776
      if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr);
777
      uint8_t keytype = Z_LVAL_PP(val_ptr);
778
      transport.writeI8(keytype);
779
      zend_hash_find(fieldspec, "vtype", 6, (void**)&val_ptr);
780
      if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr);
781
      uint8_t valtype = Z_LVAL_PP(val_ptr);
782
      transport.writeI8(valtype);
783

    
784
      zend_hash_find(fieldspec, "val", 4, (void**)&val_ptr);
785
      HashTable* valspec = Z_ARRVAL_PP(val_ptr);
786

    
787
      transport.writeI32(zend_hash_num_elements(ht));
788
      HashPosition key_ptr;
789
      for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr); zend_hash_get_current_data_ex(ht, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(ht, &key_ptr)) {
790
        binary_serialize_hashtable_key(keytype, transport, ht, key_ptr);
791
        binary_serialize(valtype, transport, val_ptr, valspec);
792
      }
793
    } return;
794
    case T_LIST: {
795
      if (Z_TYPE_PP(value) != IS_ARRAY) convert_to_array(*value);
796
      if (Z_TYPE_PP(value) != IS_ARRAY) {
797
        throw_tprotocolexception("Attempt to send an incompatible type as an array (T_LIST)", INVALID_DATA);
798
      }
799
      HashTable* ht = Z_ARRVAL_PP(value);
800
      zval** val_ptr;
801

    
802
      zend_hash_find(fieldspec, "etype", 6, (void**)&val_ptr);
803
      if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr);
804
      uint8_t valtype = Z_LVAL_PP(val_ptr);
805
      transport.writeI8(valtype);
806

    
807
      zend_hash_find(fieldspec, "elem", 5, (void**)&val_ptr);
808
      HashTable* valspec = Z_ARRVAL_PP(val_ptr);
809

    
810
      transport.writeI32(zend_hash_num_elements(ht));
811
      HashPosition key_ptr;
812
      for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr); zend_hash_get_current_data_ex(ht, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(ht, &key_ptr)) {
813
        binary_serialize(valtype, transport, val_ptr, valspec);
814
      }
815
    } return;
816
    case T_SET: {
817
      if (Z_TYPE_PP(value) != IS_ARRAY) convert_to_array(*value);
818
      if (Z_TYPE_PP(value) != IS_ARRAY) {
819
        throw_tprotocolexception("Attempt to send an incompatible type as an array (T_SET)", INVALID_DATA);
820
      }
821
      HashTable* ht = Z_ARRVAL_PP(value);
822
      zval** val_ptr;
823

    
824
      zend_hash_find(fieldspec, "etype", 6, (void**)&val_ptr);
825
      if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr);
826
      uint8_t keytype = Z_LVAL_PP(val_ptr);
827
      transport.writeI8(keytype);
828

    
829
      transport.writeI32(zend_hash_num_elements(ht));
830
      HashPosition key_ptr;
831
      for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr); zend_hash_get_current_data_ex(ht, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(ht, &key_ptr)) {
832
        binary_serialize_hashtable_key(keytype, transport, ht, key_ptr);
833
      }
834
    } return;
835
  };
836
  char errbuf[128];
837
  sprintf(errbuf, "Unknown thrift typeID %d", thrift_typeID);
838
  throw_tprotocolexception(errbuf, INVALID_DATA);
839
}
840

    
841

    
842
void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec) {
843
  HashPosition key_ptr;
844
  zval** val_ptr;
845

    
846
  TSRMLS_FETCH();
847
  zend_class_entry* ce = zend_get_class_entry(zthis TSRMLS_CC);
848

    
849
  for (zend_hash_internal_pointer_reset_ex(spec, &key_ptr); zend_hash_get_current_data_ex(spec, (void**)&val_ptr, &key_ptr) == SUCCESS; zend_hash_move_forward_ex(spec, &key_ptr)) {
850
    ulong fieldno;
851
    if (zend_hash_get_current_key_ex(spec, NULL, NULL, &fieldno, 0, &key_ptr) != HASH_KEY_IS_LONG) {
852
      throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA);
853
      return;
854
    }
855
    HashTable* fieldspec = Z_ARRVAL_PP(val_ptr);
856

    
857
    // field name
858
    zend_hash_find(fieldspec, "var", 4, (void**)&val_ptr);
859
    char* varname = Z_STRVAL_PP(val_ptr);
860

    
861
    // thrift type
862
    zend_hash_find(fieldspec, "type", 5, (void**)&val_ptr);
863
    if (Z_TYPE_PP(val_ptr) != IS_LONG) convert_to_long(*val_ptr);
864
    int8_t ttype = Z_LVAL_PP(val_ptr);
865

    
866
    zval* prop = zend_read_property(ce, zthis, varname, strlen(varname), false TSRMLS_CC);
867
    if (Z_TYPE_P(prop) != IS_NULL) {
868
      transport.writeI8(ttype);
869
      transport.writeI16(fieldno);
870
      binary_serialize(ttype, transport, &prop, fieldspec);
871
    }
872
  }
873
  transport.writeI8(T_STOP); // struct end
874
}
875

    
876
// 6 params: $transport $method_name $ttype $request_struct $seqID $strict_write
877
PHP_FUNCTION(thrift_protocol_write_binary) {
878
  int argc = ZEND_NUM_ARGS();
879
  if (argc < 6) {
880
    WRONG_PARAM_COUNT;
881
  }
882

    
883
  zval ***args = (zval***) emalloc(argc * sizeof(zval**));
884
  zend_get_parameters_array_ex(argc, args);
885

    
886
  if (Z_TYPE_PP(args[0]) != IS_OBJECT) {
887
    php_error_docref(NULL TSRMLS_CC, E_ERROR, "1st parameter is not an object (transport)");
888
    efree(args);
889
    RETURN_NULL();
890
  }
891

    
892
  if (Z_TYPE_PP(args[1]) != IS_STRING) {
893
    php_error_docref(NULL TSRMLS_CC, E_ERROR, "2nd parameter is not a string (method name)");
894
    efree(args);
895
    RETURN_NULL();
896
  }
897

    
898
  if (Z_TYPE_PP(args[3]) != IS_OBJECT) {
899
    php_error_docref(NULL TSRMLS_CC, E_ERROR, "4th parameter is not an object (request struct)");
900
    efree(args);
901
    RETURN_NULL();
902
  }
903

    
904
  try {
905
    PHPOutputTransport transport(*args[0]);
906
    const char* method_name = Z_STRVAL_PP(args[1]);
907
    convert_to_long(*args[2]);
908
    int32_t msgtype = Z_LVAL_PP(args[2]);
909
    zval* request_struct = *args[3];
910
    convert_to_long(*args[4]);
911
    int32_t seqID = Z_LVAL_PP(args[4]);
912
    convert_to_boolean(*args[5]);
913
    bool strictWrite = Z_BVAL_PP(args[5]);
914
    efree(args);
915
    args = NULL;
916

    
917
    if (strictWrite) {
918
      int32_t version = VERSION_1 | msgtype;
919
      transport.writeI32(version);
920
      transport.writeString(method_name, strlen(method_name));
921
      transport.writeI32(seqID);
922
    } else {
923
      transport.writeString(method_name, strlen(method_name));
924
      transport.writeI8(msgtype);
925
      transport.writeI32(seqID);
926
    }
927

    
928
    zval* spec = zend_read_static_property(zend_get_class_entry(request_struct TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC);
929
    if (Z_TYPE_P(spec) != IS_ARRAY) {
930
        throw_tprotocolexception("Attempt to send non-Thrift object", INVALID_DATA);
931
    }
932
    binary_serialize_spec(request_struct, transport, Z_ARRVAL_P(spec));
933
    transport.flush();
934
  } catch (const PHPExceptionWrapper& ex) {
935
    zend_throw_exception_object(ex TSRMLS_CC);
936
    RETURN_NULL();
937
  } catch (const std::exception& ex) {
938
    throw_zend_exception_from_std_exception(ex);
939
    RETURN_NULL();
940
  }
941
}
942

    
943
// 3 params: $transport $response_Typename $strict_read
944
PHP_FUNCTION(thrift_protocol_read_binary) {
945
  int argc = ZEND_NUM_ARGS();
946

    
947
  if (argc < 3) {
948
    WRONG_PARAM_COUNT;
949
  }
950

    
951
  zval ***args = (zval***) emalloc(argc * sizeof(zval**));
952
  zend_get_parameters_array_ex(argc, args);
953

    
954
  if (Z_TYPE_PP(args[0]) != IS_OBJECT) {
955
    php_error_docref(NULL TSRMLS_CC, E_ERROR, "1st parameter is not an object (transport)");
956
    efree(args);
957
    RETURN_NULL();
958
  }
959

    
960
  if (Z_TYPE_PP(args[1]) != IS_STRING) {
961
    php_error_docref(NULL TSRMLS_CC, E_ERROR, "2nd parameter is not a string (typename of expected response struct)");
962
    efree(args);
963
    RETURN_NULL();
964
  }
965

    
966
  try {
967
    PHPInputTransport transport(*args[0]);
968
    char* obj_typename = Z_STRVAL_PP(args[1]);
969
    convert_to_boolean(*args[2]);
970
    bool strict_read = Z_BVAL_PP(args[2]);
971
    efree(args);
972
    args = NULL;
973

    
974
    int8_t messageType = 0;
975
    int32_t sz = transport.readI32();
976

    
977
    if (sz < 0) {
978
      // Check for correct version number
979
      int32_t version = sz & VERSION_MASK;
980
      if (version != VERSION_1) {
981
        throw_tprotocolexception("Bad version identifier", BAD_VERSION);
982
      }
983
      messageType = (sz & 0x000000ff);
984
      int32_t namelen = transport.readI32();
985
      // skip the name string and the sequence ID, we don't care about those
986
      transport.skip(namelen + 4);
987
    } else {
988
      if (strict_read) {
989
        throw_tprotocolexception("No version identifier... old protocol client in strict mode?", BAD_VERSION);
990
      } else {
991
        // Handle pre-versioned input
992
        transport.skip(sz); // skip string body
993
        messageType = transport.readI8();
994
        transport.skip(4); // skip sequence number
995
      }
996
    }
997

    
998
    if (messageType == T_EXCEPTION) {
999
      zval* ex;
1000
      MAKE_STD_ZVAL(ex);
1001
      createObject("TApplicationException", ex);
1002
      zval* spec = zend_read_static_property(zend_get_class_entry(ex TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC);
1003
      binary_deserialize_spec(ex, transport, Z_ARRVAL_P(spec));
1004
      throw PHPExceptionWrapper(ex);
1005
    }
1006

    
1007
    createObject(obj_typename, return_value);
1008
    zval* spec = zend_read_static_property(zend_get_class_entry(return_value TSRMLS_CC), "_TSPEC", 6, false TSRMLS_CC);
1009
    binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
1010
  } catch (const PHPExceptionWrapper& ex) {
1011
    zend_throw_exception_object(ex TSRMLS_CC);
1012
    RETURN_NULL();
1013
  } catch (const std::exception& ex) {
1014
    throw_zend_exception_from_std_exception(ex);
1015
    RETURN_NULL();
1016
  }
1017
}
1018

    
(2-2/3)