From 1360270eb8e03402d48322514eaa58342e5b25d0 Mon Sep 17 00:00:00 2001 From: kufd Date: Sun, 19 Mar 2017 19:48:50 +0200 Subject: THRIFT-4126: implement required fields validation in php extension when validate compiler option is enabled Client: php This closes #1215 --- .../ext/thrift_protocol/php_thrift_protocol.cpp | 44 +++++++++++++++++++++- .../ext/thrift_protocol/php_thrift_protocol7.cpp | 42 +++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) (limited to 'lib/php') diff --git a/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp b/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp index f9b3ad76e..4c9062e80 100644 --- a/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp +++ b/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp @@ -728,6 +728,42 @@ inline bool ttypes_are_compatible(int8_t t1, int8_t t2) { return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2))); } +//is used to validate objects before serialization and after deserialization. For now, only required fields are validated. +static +void validate_thrift_object(zval* object) { + + HashPosition key_ptr; + zval** val_ptr; + + TSRMLS_FETCH(); + zend_class_entry* object_class_entry = zend_get_class_entry(object TSRMLS_CC); + HashTable* spec = Z_ARRVAL_P(zend_read_static_property(object_class_entry, "_TSPEC", 6, false TSRMLS_CC)); + + 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)) { + ulong fieldno; + if (zend_hash_get_current_key_ex(spec, NULL, NULL, &fieldno, 0, &key_ptr) != HASH_KEY_IS_LONG) { + throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA); + return; + } + HashTable* fieldspec = Z_ARRVAL_PP(val_ptr); + + // field name + zend_hash_find(fieldspec, "var", 4, (void**)&val_ptr); + char* varname = Z_STRVAL_PP(val_ptr); + + zend_hash_find(fieldspec, "isRequired", 11, (void**)&val_ptr); + bool is_required = Z_BVAL_PP(val_ptr); + + zval* prop = zend_read_property(object_class_entry, object, varname, strlen(varname), false TSRMLS_CC); + + if (is_required && Z_TYPE_P(prop) == IS_NULL) { + char errbuf[128]; + snprintf(errbuf, 128, "Required field %s.%s is unset!", object_class_entry->name, varname); + throw_tprotocolexception(errbuf, INVALID_DATA); + } + } +} + void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec) { // SET and LIST have 'elem' => array('type', [optional] 'class') // MAP has 'val' => array('type', [optiona] 'class') @@ -737,7 +773,10 @@ void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTabl zval** val_ptr = NULL; int8_t ttype = transport.readI8(); - if (ttype == T_STOP) return; + if (ttype == T_STOP) { + validate_thrift_object(zthis); + return; + } int16_t fieldno = transport.readI16(); if (zend_hash_index_find(spec, fieldno, (void**)&val_ptr) == SUCCESS) { HashTable* fieldspec = Z_ARRVAL_PP(val_ptr); @@ -903,6 +942,9 @@ void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval* void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec) { + + validate_thrift_object(zthis); + HashPosition key_ptr; zval** val_ptr; diff --git a/lib/php/src/ext/thrift_protocol/php_thrift_protocol7.cpp b/lib/php/src/ext/thrift_protocol/php_thrift_protocol7.cpp index da5b3de99..6d8b76fe9 100644 --- a/lib/php/src/ext/thrift_protocol/php_thrift_protocol7.cpp +++ b/lib/php/src/ext/thrift_protocol/php_thrift_protocol7.cpp @@ -849,6 +849,44 @@ bool ttypes_are_compatible(int8_t t1, int8_t t2) { return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2))); } +//is used to validate objects before serialization and after deserialization. For now, only required fields are validated. +static +void validate_thrift_object(zval* object) { + zend_class_entry* object_class_entry = Z_OBJCE_P(object); + zval* is_validate = zend_read_static_property(object_class_entry, "isValidate", sizeof("isValidate")-1, false); + zval* spec = zend_read_static_property(object_class_entry, "_TSPEC", sizeof("_TSPEC")-1, false); + HashPosition key_ptr; + zval* val_ptr; + + if (Z_TYPE_INFO_P(is_validate) == IS_TRUE) { + for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(spec), &key_ptr); + (val_ptr = zend_hash_get_current_data_ex(Z_ARRVAL_P(spec), &key_ptr)) != nullptr; + zend_hash_move_forward_ex(Z_ARRVAL_P(spec), &key_ptr)) { + + zend_ulong fieldno; + if (zend_hash_get_current_key_ex(Z_ARRVAL_P(spec), nullptr, &fieldno, &key_ptr) != HASH_KEY_IS_LONG) { + throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA); + return; + } + HashTable* fieldspec = Z_ARRVAL_P(val_ptr); + + // field name + zval* zvarname = zend_hash_str_find(fieldspec, "var", sizeof("var")-1); + char* varname = Z_STRVAL_P(zvarname); + + zval* is_required = zend_hash_str_find(fieldspec, "isRequired", sizeof("isRequired")-1); + zval rv; + zval* prop = zend_read_property(object_class_entry, object, varname, strlen(varname), false, &rv); + + if (Z_TYPE_INFO_P(is_required) == IS_TRUE && Z_TYPE_P(prop) == IS_NULL) { + char errbuf[128]; + snprintf(errbuf, 128, "Required field %s.%s is unset!", ZSTR_VAL(object_class_entry->name), varname); + throw_tprotocolexception(errbuf, INVALID_DATA); + } + } + } +} + static void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec) { // SET and LIST have 'elem' => array('type', [optional] 'class') @@ -857,6 +895,7 @@ void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTabl while (true) { int8_t ttype = transport.readI8(); if (ttype == T_STOP) { + validate_thrift_object(zthis); return; } @@ -892,6 +931,9 @@ void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTabl static void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec) { + + validate_thrift_object(zthis); + HashPosition key_ptr; zval* val_ptr; -- cgit v1.2.1