/* $Id: php_params.c,v 1.1 2007/11/13 11:13:55 pollita Exp $ */ #include "php_params.h" #include "ext/standard/info.h" #define PHP_PARAMS_EXTNAME "params" #define PHP_PARAMS_EXTVER "1.0" static inline int php_params_parse_format(const char *format, int format_len, int *prequired, int *pvar_args, int *ppost_var_args) { int required = 0; int var_args = 0; int post_var_args = 0; int post_optional = 0; const char *p = format; while (*p) { switch (*(p++)) { case 'b': case 'd': case 'l': case 's': case 'a': case 'o': case 'O': case 'r': case 'R': case 'z': if (!post_optional) { required++; } if (var_args) { post_var_args++; } break; case '+': if (!post_optional) { required++; } /* fallthrough */ case '*': if (var_args) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Limit one var args specifier per parse call"); return FAILURE; } var_args = 1; break; case '|': if (post_optional) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Limit one optionality modifier per parse call"); return FAILURE; } post_optional = 1; break; default: php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Unrecognized format specifier '%c', ignoring...", *p); } } *prequired = required; *pvar_args = var_args; *ppost_var_args = post_var_args; return SUCCESS; } #if (PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION > 1) # define PHP_PARAMS_INT_CAST (int)(zend_uintptr_t) #else # define PHP_PARAMS_INT_CAST (ulong) #endif static inline int php_params_get_args(int *pargc, zval ***pargv TSRMLS_DC) { void **p = EG(argument_stack).top_element - 2; int pp_arg_count = PHP_PARAMS_INT_CAST *p; p -= 1 + pp_arg_count; if (*p) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot use this function in a function call"); return FAILURE; } --p; if (p < EG(argument_stack).elements) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot use this function from the global scope"); return FAILURE; } *pargc = PHP_PARAMS_INT_CAST *p; *pargv = (zval**)(p - *pargc); return SUCCESS; } #define php_params_pop_bool(rv, arg) add_next_index_bool(rv, arg ? zend_is_true(arg) : 0) static inline void php_params_pop_long(zval *rv, zval *arg) { long lval = 0; if (arg) { zval copyval = *arg; zval_copy_ctor(©val); convert_to_long(©val); lval = Z_LVAL(copyval); } add_next_index_long(rv, lval); } static inline void php_params_pop_double(zval *rv, zval *arg) { double dval = 0.0; if (arg) { zval copyval = *arg; zval_copy_ctor(©val); convert_to_double(©val); dval = Z_DVAL(copyval); } add_next_index_double(rv, dval); } static inline void php_params_pop_string(zval *rv, zval *arg) { char *sval = ""; int slen = 0; if (arg) { zval copyval = *arg; zval_copy_ctor(©val); convert_to_string(©val); sval = Z_STRVAL(copyval); slen = Z_STRLEN(copyval); } else { sval = STR_EMPTY_ALLOC(); } add_next_index_stringl(rv, sval, slen, 0); } static inline void php_params_pop_array(zval *rv, zval *arg) { zval *ret; MAKE_STD_ZVAL(ret); if (arg) { *ret = *arg; zval_copy_ctor(ret); INIT_PZVAL(ret); convert_to_array(ret); } else { array_init(ret); } add_next_index_zval(rv, ret); } static inline int php_parse_object_is_a(zval *obj, zval *type) { char *lcClass = estrndup(Z_STRVAL_P(type), Z_STRLEN_P(type)); int lcLen = Z_STRLEN_P(type); zend_class_entry *ce, *objce = Z_OBJCE_P(obj); php_strtolower(lcClass, lcLen); if (zend_hash_find(EG(class_table), lcClass, lcLen + 1, (void**)&ce) == FAILURE) { efree(lcClass); return 0; } efree(lcClass); #ifdef ZEND_ENGINE_2 ce = *((zend_class_entry**)ce); #endif while (objce) { if (objce == ce) { return 1; } objce = objce->parent; } return 0; } static inline int php_params_pop_object(zval *rv, zval *arg, zval **types, int argn TSRMLS_DC) { if (!arg) { add_next_index_null(rv); return SUCCESS; } if (!types) { goto add_object; } if (Z_TYPE_PP(types) != IS_ARRAY && Z_TYPE_PP(types) != IS_STRING && (Z_TYPE_PP(types) != IS_BOOL || !Z_BVAL_PP(types)) ) { /* Invalid type hint */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid type hint for argument %d", argn + 1); return FAILURE; } if (Z_TYPE_P(arg) != IS_OBJECT) { /* Type hinted, but not an object */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to convert argument %d to an object of specific type", argn + 1); return FAILURE; } if (Z_TYPE_PP(types) == IS_STRING) { if (!php_parse_object_is_a(arg, *types)) { /* Type hinted and wrong type */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument %d must be a class of type %s, but is of type %s", argn + 1, Z_STRVAL_PP(types), Z_OBJCE_P(arg)->name); return FAILURE; } goto add_object; } if (Z_TYPE_PP(types) == IS_ARRAY) { HashPosition pos; zval **type; for(zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(types), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_PP(types), (void**)&type, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_PP(types), &pos)) { if (Z_TYPE_PP(type) != IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Invalid class hint for argument %d, ignoring", argn + 1); continue; } if (php_parse_object_is_a(arg, *type)) { goto add_object; } } php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument %d must be a class of one of the specified typed, but it is not", argn + 1); return FAILURE; } add_object: { zval *copyval; MAKE_STD_ZVAL(copyval); *copyval = *arg; zval_copy_ctor(copyval); INIT_PZVAL(copyval); convert_to_object(copyval); add_next_index_zval(rv, copyval); return SUCCESS; } } static inline int php_params_pop_resource(zval *rv, zval *arg, zval **types, int argn TSRMLS_DC) { const char *typename; int typename_len; if (!arg) { add_next_index_null(rv); return SUCCESS; } if (Z_TYPE_P(arg) != IS_RESOURCE) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument %d must be a resource", argn + 1); return FAILURE; } if (!types) { goto add_resource; } if (Z_TYPE_PP(types) != IS_STRING && Z_TYPE_PP(types) != IS_ARRAY) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid resource type filter for argument %d", argn + 1); return FAILURE; } typename = zend_rsrc_list_get_rsrc_type(Z_RESVAL_P(arg) TSRMLS_CC); if (!typename) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to verify resource type for argument %d, this should never happen", argn + 1); return FAILURE; } typename_len = strlen(typename); if (Z_TYPE_PP(types) == IS_STRING) { if (Z_STRLEN_PP(types) != typename_len || strncasecmp(Z_STRVAL_PP(types), typename, typename_len)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument %d must be a resource of type %s, %s given", argn + 1, Z_STRVAL_PP(types), typename); return FAILURE; } goto add_resource; } if (Z_TYPE_PP(types) == IS_ARRAY) { HashPosition pos; zval **type; for(zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(types), &pos); zend_hash_get_current_data_ex(Z_ARRVAL_PP(types), (void**)&type, &pos) == SUCCESS; zend_hash_move_forward_ex(Z_ARRVAL_PP(types), &pos)) { if (Z_TYPE_PP(type) != IS_STRING) { php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Invalid resource hint for argument %d, ignoring", argn + 1); continue; } if (Z_STRLEN_PP(type) == typename_len && strncasecmp(Z_STRVAL_PP(type), typename, typename_len) == 0) { goto add_resource; } } php_error_docref(NULL TSRMLS_CC, E_WARNING, "Argument %d is of type %s, which does not match any supplied type", argn + 1, typename); return FAILURE; } add_resource: { zval *copyval; MAKE_STD_ZVAL(copyval); *copyval = *arg; zval_copy_ctor(copyval); INIT_PZVAL(copyval); add_next_index_zval(rv, copyval); return SUCCESS; } } static inline void php_params_pop_zval(zval *rv, zval *arg) { zval *ret; MAKE_STD_ZVAL(ret); if (arg) { *ret = *arg; zval_copy_ctor(ret); INIT_PZVAL(ret); } add_next_index_zval(rv, ret); } /* {{{ proto mixed params_parse(string $format, ...) * * Types: * b => boolean * d => double * l => long * s => string * a => array * o => object * O => object, type(s) * r => resource * R => resource, type(s) * z => any * + => one or more of any * * => zero or more of any * * | => Args to the left are required, args to the right are optional */ static PHP_FUNCTION(params_parse) { int parsec = ZEND_NUM_ARGS(); zval ***parsev = safe_emalloc(parsec, sizeof(zval**), 0); int required_num_args, actual_num_args; char *format; int format_len, var_args, post_var_args, argc, parsen = 1, argn = 0; zval **argv; if (FAILURE == zend_get_parameters_array_ex(parsec, parsev TSRMLS_CC)) { efree(parsev); return; } convert_to_string_ex(parsev[0]); format = Z_STRVAL_PP(parsev[0]); format_len = Z_STRLEN_PP(parsev[0]); if (FAILURE == php_params_parse_format(format, format_len, &required_num_args, &var_args, &post_var_args)) { efree(parsev); RETURN_FALSE; } if (FAILURE == php_params_get_args(&argc, &argv TSRMLS_CC)) { efree(parsev); RETURN_FALSE; } if (!EG(current_execute_data) || !EG(current_execute_data)->op_array || EG(current_execute_data)->op_array->type != ZEND_USER_FUNCTION) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Must be called from a userspace function or method"); efree(parsev); RETURN_FALSE; } if (argc < required_num_args) { #ifdef ZEND_ENGINE_2 const char *calling_class = EG(current_execute_data)->op_array->scope ? EG(current_execute_data)->op_array->scope->name : NULL; #else const char *calling_class = EG(current_execute_data)->ce ? EG(current_execute_data)->ce->name : NULL; #endif php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s%s%s expects at least %d arguments, %d given", calling_class ? calling_class : "", calling_class ? "::" : "", EG(current_execute_data)->op_array->function_name, required_num_args, argc); efree(parsev); RETURN_FALSE; } array_init(return_value); while (format_len > 0) { zval *arg = (argn < argc) ? argv[argn] : NULL; zval **extra = (parsen < parsec) ? parsev[parsen] : NULL; int ok = SUCCESS; switch (*format) { case 'b': php_params_pop_bool(return_value, arg); argn++; break; case 'l': php_params_pop_long(return_value, arg); argn++; break; case 'd': php_params_pop_double(return_value, arg); argn++; break; case 's': php_params_pop_string(return_value, arg); argn++; break; case 'a': php_params_pop_array(return_value, arg); argn++; break; case 'o': ok = php_params_pop_object(return_value, arg, NULL, argn TSRMLS_CC); argn++; break; case 'O': ok = php_params_pop_object(return_value, arg, extra, argn TSRMLS_CC); argn++; parsen++; break; case 'r': ok = php_params_pop_resource(return_value, arg, NULL, argn TSRMLS_CC); argn++; break; case 'R': ok = php_params_pop_resource(return_value, arg, extra, argn TSRMLS_CC); argn++; parsen++; break; case 'z': php_params_pop_zval(return_value, arg); argn++; break; case '+': case '*': { /* Total arguments minus those which have already been consumed minus those which should be saved for the end of the list */ int var_count = (argc - argn) - post_var_args, i; zval *var_args; MAKE_STD_ZVAL(var_args); array_init(var_args); if (var_count > 0) { for(i = 0; i < var_count; i++) { php_params_pop_zval(var_args, argv[argn + i]); } argn += var_count; } add_next_index_zval(return_value, var_args); break; } /* Ignore anything else, a notice was already raised */ } if (ok == FAILURE) { /* The object or resource fetch failed and raised a warning, bailout */ zval_dtor(return_value); RETVAL_FALSE; break; } format++; format_len--; } efree(parsev); } /* }}} */ /* {{{ */ static PHP_MINFO_FUNCTION(params) { php_info_print_table_start(); php_info_print_table_row(2, "params support", "enabled"); php_info_print_table_end(); } /* }}} */ #ifdef ZEND_ENGINE_2 static ZEND_BEGIN_ARG_INFO_EX(params_parse_arginfo, 0, ZEND_RETURN_VALUE, 1) ZEND_ARG_INFO(0, format) ZEND_END_ARG_INFO() #else # define params_parse_arginfo NULL #endif /* {{{ params_functions[] */ static zend_function_entry params_functions[] = { PHP_FE(params_parse, params_parse_arginfo) {NULL, NULL, NULL} }; /* }}} */ /* {{{ params_module_entry */ zend_module_entry params_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_PARAMS_EXTNAME, params_functions, NULL, /* MINIT */ NULL, /* MSHUTDOWN */ NULL, /* RINIT */ NULL, /* RSHUTDOWN */ PHP_MINFO(params), #if ZEND_MODULE_API_NO >= 20010901 PHP_PARAMS_EXTVER, /* Replace with version number for your extension */ #endif STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_PARAMS ZEND_GET_MODULE(params) #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */