#ifdef HAVE_CONFIG_H #include "config.h" #endif #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" #include "Zend/zend_exceptions.h" #include "php_bbcode.h" /* XXX: only required for php_debug_zval_dump */ #include "ext/standard/php_var.h" /* XXX: required for pcre_cache_entry */ #include "ext/pcre/php_pcre.h" /* If you declare any globals in php_bbcode.h uncomment this: ZEND_DECLARE_MODULE_GLOBALS(bbcode) */ typedef struct _bbcode_object { HashTable *tag; HashTable *smiley; zend_string *root; zend_long flag; zend_object std; } bbcode_object; /* True global resources - no need for thread safety here */ zend_object_handlers bbcode_handlers; /* BBCode object */ zend_class_entry *bbcode_ce; /* BBCode exception object */ zend_class_entry *bbcode_exception_ce; /* Define all constants */ #ifdef old_code_dropped GEN(TYPE_ARG) /*BBCODE_TYPE_ARG*/\ GEN(TYPE_NOARG) /*BBCODE_TYPE_NOARG*/\ GEN(TYPE_OPTARG) /*BBCODE_TYPE_OPTARG*/\ #endif #define BBCODE_DEF(GEN) \ /* Types */ \ GEN(TYPE_ROOT) /*BBCODE_TYPE_ROOT*/\ GEN(TYPE_MULTI) /*BBCODE_TYPE_ARG|BBCODE_TYPE_OPTARG|BBCODE_TYPE_NOARG*/\ GEN(TYPE_SINGLE) /*BBCODE_TYPE_SINGLE*/\ /* TODO: add again each feature when implemented (before beta) */ #if 0 /* Quotes */ \ GEN(QUOTE_DOUBLE) /*BBCODE_ARG_DOUBLE_QUOTE*/\ GEN(QUOTE_SIMPLE) /*BBCODE_ARG_SINGLE_QUOTE*/\ GEN(QUOTE_HTML) /*BBCODE_ARG_HTML_QUOTE*/\ GEN(QUOTE_ESCAPE) /*BBCODE_ARG_QUOTE_ESCAPING*/\ \ /* Corrections */ \ GEN(CORRECT_AUTO) /*BBCODE_AUTO_CORRECT*/\ GEN(CORRECT_REOPEN) /*BBCODE_CORRECT_REOPEN_TAGS*/\ GEN(CORRECT_NOTREE) /*BBCODE_DISABLE_TREE_BUILD*/\ \ /* Smileys */ \ GEN(SMILEY_ON) /*BBCODE_DEFAULT_SMILEYS_ON*/\ GEN(SMILEY_CI) /*BBCODE_SMILEYS_CASE_INSENSITIVE*/\ \ /* Flags */ \ GEN(CLOSE_AUTO) /*BBCODE_FLAGS_ONE_OPEN_PER_LEVEL*/\ GEN(REMOVE_EMPTY) /*BBCODE_FLAGS_REMOVE_IF_EMPTY*/\ GEN(REMOVE_CONTENT) /*BBCODE_FLAGS_CDATA_NOT_ALLOWED*/\ GEN(REMOVE_REOPEN) /*BBCODE_FLAGS_DENY_REOPEN_CHILD*/ #endif /* Generate enum version */ #define BBCODE_GEN_ENUM(NAME) BBCODE_ ## NAME, /* Generate zend const version */ #define BBCODE_GEN_CONST(NAME) zend_declare_class_constant_long(bbcode_ce, #NAME, sizeof(#NAME) - 1, BBCODE_ ## NAME TSRMLS_CC); /* Defined in /usr/include/php/Zend/zend_types.h +382 */ #define BBCODE_GET_TYPE(TYPE) (TYPE==IS_UNDEF)?"undef":( \ (TYPE==IS_NULL)?"null":( \ (TYPE==IS_FALSE)?"false":( \ (TYPE==IS_TRUE)?"true":( \ (TYPE==IS_LONG)?"long":( \ (TYPE==IS_DOUBLE)?"double":( \ (TYPE==IS_STRING)?"string":( \ (TYPE==IS_ARRAY)?"array":( \ (TYPE==IS_OBJECT)?"object":( \ (TYPE==IS_RESOURCE)?"resource":( \ (TYPE==IS_REFERENCE)?"reference":( \ (TYPE==IS_CONSTANT_AST)?"constant":"unknown"\ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) \ ) /* All flags enum set */ enum { BBCODE_DEF(BBCODE_GEN_ENUM) }; /* Set default flag * TODO: Implement it in an ini file ? (later maybe) */ #define BBCODE_DEFAULT_FLAG 0 #if 0 #define BBCODE_DEFAULT_FLAG BBCODE_QUOTE_DOUBLE|BBCODE_QUOTE_SIMPLE|BBCODE_QUOTE_ESCAPE|BBCODE_CORRECT_AUTO|BBCODE_CORRECT_REOPEN|BBCODE_SMILEY_ON|BBCODE_CLOSE_AUTO|BBCODE_REMOVE_EMPTY #endif /* Set debug flag */ #define BBCODE_DEBUG 0 /* {{{ Function prototypes (bbcode_destroy|bbcode_free|bbcode_clone|bbcode_create) */ static void bbcode_destroy(zend_object *obj TSRMLS_DC); static void bbcode_free(zend_object *obj TSRMLS_DC); static zend_object *bbcode_clone(zval *obj TSRMLS_DC); static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC); /* }}} */ /* {{{ static inline bbcode_object* bbcode_fetch(zend_object *obj TSRMLS_DC) { * BBCode object fetch function */ static inline bbcode_object* bbcode_fetch(zend_object *obj TSRMLS_DC) { return (bbcode_object *)((char *)obj - XtOffsetOf(bbcode_object, std)); } /* }}} */ /* {{{ Z_BBCODE_P(zv) bbcode_fetch(Z_OBJ_P((zv))) * BBCode object pointer fetch macro */ #define Z_BBCODE_P(zv) bbcode_fetch(Z_OBJ_P((zv))) /* }}} */ /* {{{ static void bbcode_destroy(zend_object *obj TSRMLS_DC) { * BBCode object destroy call */ static void bbcode_destroy(zend_object *obj TSRMLS_DC) { /* Trigger user land destructor on obj */ zend_objects_destroy_object(obj); } /* }}} */ /* {{{ static void bbcode_free(zend_object *obj TSRMLS_DC) { * BBCode object free call */ static void bbcode_free(zend_object *obj TSRMLS_DC) { /* Retrieve bbcode object from zend object */ bbcode_object *bbobj = bbcode_fetch(obj); /* Check if root is present */ if (bbobj->root) { /* Release the root string */ zend_string_release(bbobj->root); } /* Check if tag is present */ if (bbobj->tag) { /* Release the tag hash */ zend_array_destroy(bbobj->tag); } /* Check if smiley is present */ if (bbobj->smiley) { /* Release the smiley hash */ zend_array_destroy(bbobj->smiley); } /* Trigger zend object std destructor on obj */ zend_object_std_dtor(obj); /* Free the bbobj */ efree(bbobj); } /* }}} */ /* {{{ static zend_object *bbcode_clone(zval *obj TSRMLS_DC) { * BBCode object clone call */ static zend_object *bbcode_clone(zval *obj TSRMLS_DC) { /* Fetch pointer to old bbcode object */ bbcode_object *oldbbobj = Z_BBCODE_P(obj); /* Create pointer on new obj */ //bbcode_object *newbbobj = bbcode_fetch(bbcode_create(oldbbobj->std.ce)); bbcode_object *newbbobj = bbcode_fetch(bbcode_create(Z_OBJ_P(obj)->ce)); /* Call members cloning function */ zend_objects_clone_members(&newbbobj->std, &oldbbobj->std); /* Duplicate flag value */ newbbobj->flag = oldbbobj->flag; /* Set root as null */ newbbobj->root = NULL; /* Check if root is available */ if (oldbbobj->root) { /* Duplicate root zend string */ newbbobj->root = zend_string_copy(oldbbobj->root); } /* Set tag as null */ newbbobj->tag = NULL; /* Check if tag is available */ if (oldbbobj->tag) { /* Duplicate tag zend array */ newbbobj->tag = zend_array_dup(oldbbobj->tag); } /* Set smiley as null */ newbbobj->smiley = NULL; /* Check if smiley is available */ if (oldbbobj->smiley) { /* Duplicate smiley zend array */ newbbobj->smiley = zend_array_dup(oldbbobj->smiley); } /* Return the new object */ return &newbbobj->std; } /* }}} */ /* {{{ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) { * BBCode object create call */ static zend_object *bbcode_create(zend_class_entry *ce TSRMLS_DC) { /* Allocate object */ bbcode_object *obj = (bbcode_object *)ecalloc(1, sizeof(bbcode_object) + zend_object_properties_size(ce)); /* Init std member */ zend_object_std_init(&obj->std, ce TSRMLS_CC); /* Set object properties */ object_properties_init(&obj->std, ce TSRMLS_CC); /* Duplicate standard object handler */ memcpy(&bbcode_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); /* Set object offset */ bbcode_handlers.offset = XtOffsetOf(bbcode_object, std); /* Set destructor function */ bbcode_handlers.dtor_obj = (zend_object_dtor_obj_t) bbcode_destroy; /* Set free function */ bbcode_handlers.free_obj = (zend_object_free_obj_t) bbcode_free; /* Set clone function */ bbcode_handlers.clone_obj = (zend_object_clone_obj_t) bbcode_clone; /* Assign custom handler */ obj->std.handlers = &bbcode_handlers; /* Init tag */ obj->tag = NULL; /* Init smiley */ obj->smiley = NULL; /* Init root */ obj->root = NULL; /* Init flag */ obj->flag = BBCODE_DEFAULT_FLAG; /* Return the std member address */ return &obj->std; } /* }}} */ /* {{{ ZEND_BEGIN_ARG_INFO_EX(bbcode_construct_arginfo) */ // ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) ZEND_BEGIN_ARG_INFO_EX(bbcode_construct_arginfo, 0, 0, 1) // ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) ZEND_ARG_ARRAY_INFO(0, tag, 0) ZEND_ARG_ARRAY_INFO(0, smiley, 1) // ZEND_ARG_INFO(pass_by_ref, name) ZEND_ARG_INFO(0, flag) ZEND_END_ARG_INFO() /* }}} */ /* {{{ ZEND_BEGIN_ARG_INFO_EX(bbcode_parse_arginfo) */ // ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) ZEND_BEGIN_ARG_INFO_EX(bbcode_parse_arginfo, 0, 0, 1) // ZEND_ARG_INFO(pass_by_ref, name) ZEND_ARG_INFO(0, str) ZEND_END_ARG_INFO() /* }}} */ /* {{{ static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */ static HashTable *bbcode_gen_child(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { /* Key string */ zend_string *key; /* Value */ zval *val; /* Iterate on each key val until hash end is reached */ ZEND_HASH_FOREACH_STR_KEY_VAL(tag, key, val) { /* Check if key is not a string, value is null or not an array */ if (key == NULL || val == NULL || Z_TYPE_P(val) != IS_ARRAY) { /* Return failure, useless to generate a hash when we will trigger an error later */ return NULL; } /* Init type val */ zval *type; /* Check that we have no type or that it's not root type */ /* XXX: it's not possible to have in child the root tag */ if ((type = zend_hash_str_find(Z_ARR_P(val), "type", strlen("type"))) == NULL || Z_LVAL_P(type) != BBCODE_TYPE_ROOT) { /* Check that we are not on cur key */ /* XXX: not really required, but it's stupid to allow same tag in child */ //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) { /* Init parent val */ zval *parent; /* Init toInsert bool */ unsigned char toInsert = 0; /* Check if we find parent key */ if ((parent = zend_hash_str_find(Z_ARR_P(val), "parent", strlen("parent"))) != NULL) { /* Parent value */ zval *pval; /* Iterate on each val until hash end is reached */ ZEND_HASH_FOREACH_VAL(Z_ARR_P(parent), pval) { /* Check that parent val is a string */ if (Z_TYPE_P(pval) != IS_STRING) { return NULL; } /* Check that tkey is a string and is identical */ if (strcmp(Z_STRVAL_P(pval), ZSTR_VAL(cur)) == 0) { toInsert = 1; } } ZEND_HASH_FOREACH_END(); /* No parent key */ } else { toInsert = 1; } /* Add the key */ if (toInsert) { /* Init child val */ zval cval; /* Set child val */ ZVAL_STR(&cval, key); /* Insert the child val in return array */ ZEND_ASSERT(zend_hash_next_index_insert_new(ret, &cval) != NULL); } //} } } ZEND_HASH_FOREACH_END(); /* Return value */ return ret; } /* }}} */ /* {{{ static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { */ static HashTable *bbcode_gen_parent(HashTable *ret, HashTable *tag, zend_string *cur TSRMLS_DC) { /* Key string */ zend_string *key; /* Value */ zval *val; /* Iterate on each key val until hash end is reached */ ZEND_HASH_FOREACH_STR_KEY_VAL(tag, key, val) { /* Check if key is not a string, value is null or not an array */ if (key == NULL || val == NULL || Z_TYPE_P(val) != IS_ARRAY) { /* Return failure, useless to generate a hash when we will trigger an error later */ return NULL; } /* Init type val */ zval *type; /* Check that we have no type or that it's not single type */ /* XXX: it's not possible to have in child the root tag */ if ((type = zend_hash_str_find(Z_ARR_P(val), "type", strlen("type"))) == NULL || Z_LVAL_P(type) != BBCODE_TYPE_SINGLE) { /* Check that we are not on cur key */ /* XXX: not really required, but it's stupid to allow same tag in child */ //if (strcmp(ZSTR_VAL(cur), ZSTR_VAL(key)) != 0) { /* Init child val */ zval *child; /* Init toInsert bool */ unsigned char toInsert = 0; /* Check if we find child key */ if ((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) { /* Child value */ zval *cval; /* Iterate on each val until hash end is reached */ ZEND_HASH_FOREACH_VAL(Z_ARR_P(child), cval) { /* Check that child val is a string */ if (Z_TYPE_P(cval) != IS_STRING) { return NULL; } /* Check that tkey is a string and is identical */ if (strcmp(Z_STRVAL_P(cval), ZSTR_VAL(cur)) == 0) { toInsert = 1; } } ZEND_HASH_FOREACH_END(); /* No child key */ } else { toInsert = 1; } /* Add the key */ if (toInsert) { /* Init parent val */ zval pval; /* Set parent val */ ZVAL_STR(&pval, key); /* Insert the parent val in return array */ ZEND_ASSERT(zend_hash_next_index_insert_new(ret, &pval) != NULL); } //} } } ZEND_HASH_FOREACH_END(); /* Return value */ return ret; } /* }}} */ /* {{{ static zend_bool *bbcode_check_tag(HashTable *tag TSRMLS_DC) { * Check that tag hash is valid */ static zend_string *bbcode_check_tag(HashTable *tag TSRMLS_DC) { /* Key numeric value */ zend_long idx; /* Key and root string */ zend_string *key, *root = NULL; /* Value */ zval *val; /* Key position */ int pos = 0; /* Has a root */ int hasRoot = 0; /* Iterate on each key val until hash end is reached */ ZEND_HASH_FOREACH_KEY_VAL(tag, idx, key, val) { /* Check if key is not a string */ if (key == NULL) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument tag is not an expected string", idx, pos); /* Return failure */ return NULL; /* Check that key do not contains a special character */ } else if (strchr(ZSTR_VAL(key), '=') != NULL || strchr(ZSTR_VAL(key), '[') != NULL || strchr(ZSTR_VAL(key), ']') != NULL || strchr(ZSTR_VAL(key), '(') != NULL || strchr(ZSTR_VAL(key), ')') != NULL || strchr(ZSTR_VAL(key), '/') != NULL || strchr(ZSTR_VAL(key), '\\') != NULL) { /* Display error about key containing a special char */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag contains a special forbidden character: =[]()/\\", ZSTR_VAL(key), pos); /* Return failure */ return NULL; } /* Check that value exists */ if (val == NULL) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos); /* Return failure */ return NULL; /* Check that value is an array */ } else if (Z_TYPE_P(val) != IS_ARRAY) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, BBCODE_GET_TYPE(Z_TYPE_P(val)), Z_TYPE_P(val)); /* Return failure */ return NULL; } /* Store child tag array */ HashTable *ctag = Z_ARR_P(val); /* Child key numeric value */ zend_long cidx; /* Child key string */ zend_string *ckey; /* Child value */ zval *cval; /* Buffer */ char *buf; /* Child key position */ int cpos = 0, type, len; /* Detect if entry has a type, parent, child, open, close, default or arg key to display error if mandatory field is missing */ unsigned char hasType = 0, hasParent = 0, hasChild = 0, hasOpen = 0, hasClose = 0, hasDefault = 0, hasArg = 0; /* Iterate on each ckey cval until hash end is reached */ ZEND_HASH_FOREACH_KEY_VAL(ctag, cidx, ckey, cval) { /* Check if ckey is a string */ if (!ckey) { /* Display error about long ckey */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%ld[%d] from argument tag is not an expected string", ZSTR_VAL(key), pos, cidx, cpos); /* Return failure */ return NULL; } /* Check if ckey is empty */ if (ZSTR_LEN(ckey) == 0) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%s[%d] from argument tag is empty not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; /* Check if current ckey is type */ } else if (strcmp("type", ZSTR_VAL(ckey)) == 0) { /* Extract type */ type = Z_LVAL_P(cval); /* Check that current type is valid */ if ( type != BBCODE_TYPE_ROOT && type != BBCODE_TYPE_MULTI && type != BBCODE_TYPE_SINGLE ) { /* Display error about unexpected type value */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value '%d' for key %s[%d]/%s[%d] from argument tag is not one of expected BBCODE::TYPE_(ROOT|MULTI|SINGLE) constant", type, ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; /* Check if root */ } else if (type == BBCODE_TYPE_ROOT) { /* Grow hasRoot */ hasRoot++; } /* Set hasType */ hasType = 1; /* Check if current key is parent */ } else if (strcmp("parent", ZSTR_VAL(ckey)) == 0) { /* Check that value exists */ if (cval == NULL) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; /* Check that value is an array */ } else if (Z_TYPE_P(cval) != IS_ARRAY) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval)); /* Return failure */ return NULL; /* Check that value is an non empty array */ } else if (zend_array_count(Z_ARR_P(cval)) == 0) { /* Display error about empty parent array */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is an empty array instead of expected filled array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; } /* Parent value */ zval *pval; /* Child key position */ int ppos = 0; /* Iterate on each val until hash end is reached */ ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval), pval) { /* Check that parent val is a string */ /* XXX: pval == NULL case is catched here too */ if (Z_TYPE_P(pval) != IS_STRING) { /* Display error about not string */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, ppos, BBCODE_GET_TYPE(Z_TYPE_P(pval)), Z_TYPE_P(pval)); /* Return failure */ return NULL; } /* Tag key */ zend_string *tkey; /* Reset found flag */ unsigned char found = 0; /* Iterate on each key until hash end is reached */ ZEND_HASH_FOREACH_STR_KEY(tag, tkey) { /* Check that tkey is a string and is identical */ if (tkey != NULL && strcmp(Z_STRVAL_P(pval), ZSTR_VAL(tkey)) == 0) { /* We found parent value in tag keys*/ found = 1; } } ZEND_HASH_FOREACH_END(); /* Check if we found the key */ if (!found) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/%s[%d] from argument tag is not present in tag keys", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, Z_STRVAL_P(pval), ppos); /* Return failure */ return NULL; } /* Grow ppos */ ppos++; } ZEND_HASH_FOREACH_END(); /* Set hasParent */ hasParent = 1; /* Check if current key is child */ } else if (strcmp("child", ZSTR_VAL(ckey)) == 0) { /* Check that value exists */ if (cval == NULL) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is NULL instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; /* Check that value is an array */ } else if (Z_TYPE_P(cval) != IS_ARRAY) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d] from argument tag is %s[%d] instead of expected array", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, BBCODE_GET_TYPE(Z_TYPE_P(cval)), Z_TYPE_P(cval)); /* Return failure */ return NULL; } /* Child value */ zval *ccval; /* Child key position */ int ccpos = 0; /* Iterate on each val until hash end is reached */ ZEND_HASH_FOREACH_VAL(Z_ARR_P(cval), ccval) { /* Check that child val is a string */ /* XXX: ccval == NULL case is catched here too */ if (Z_TYPE_P(ccval) != IS_STRING) { /* Display error about not string */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d]/%s[%d]/[%d] from argument tag is %s[%d] instead of expected string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos, ccpos, BBCODE_GET_TYPE(Z_TYPE_P(ccval)), Z_TYPE_P(ccval)); /* Return failure */ return NULL; } /* Tag key */ zend_string *tkey; /* Reset found flag */ unsigned char found = 0; /* Iterate on each key until hash end is reached */ ZEND_HASH_FOREACH_STR_KEY(tag, tkey) { /* Check that tkey is a string and is identical */ if (tkey != NULL && strcmp(Z_STRVAL_P(ccval), ZSTR_VAL(tkey)) == 0) { /* We found child value in tag keys*/ found = 1; } } ZEND_HASH_FOREACH_END(); /* Check if we found the key */ if (!found) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Child value %s for key %s[%d]/%s[%d] is not present in tag keys", Z_STRVAL_P(ccval), ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; } /* Grow ccpos */ ccpos++; } ZEND_HASH_FOREACH_END(); /* Set hasChild */ hasChild = 1; /* Check if current key is open */ } else if (strcmp("open", ZSTR_VAL(ckey)) == 0) { /* Set hasOpen */ hasOpen = 1; /* Check if current key is close */ } else if (strcmp("close", ZSTR_VAL(ckey)) == 0) { /* Set hasClose */ hasClose = 1; /* Check if current key is default */ } else if (strcmp("default", ZSTR_VAL(ckey)) == 0) { /* Set hasDefault */ hasDefault = 1; /* Check if current key is arg */ } else if (strcmp("arg", ZSTR_VAL(ckey)) == 0) { /* Set hasArg */ hasArg = 1; /* Check if current key is unknown */ } else { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d]/%s[%d] from argument tag is not one of expected type|parent|child|open|close|default string", ZSTR_VAL(key), pos, ZSTR_VAL(ckey), cpos); /* Return failure */ return NULL; } /* Grow cpos */ cpos++; } ZEND_HASH_FOREACH_END(); /* Check if val array is immutable */ /* XXX: this strange case happen with empty val array, the zend engine don't initialize a usable hashtable, to avoid triggering a segfault later when using the array, we fix it here */ if ((Z_GC_FLAGS_P(val) & GC_IMMUTABLE) == GC_IMMUTABLE) { /* Allocate hash table */ /* XXX: this is required to avoid segfault in zend_hash_add when the array is immutable */ ALLOC_HASHTABLE(Z_ARR_P(val)); /* Init hash table */ zend_hash_init(Z_ARR_P(val), 0, NULL, ZVAL_PTR_DTOR, 0); /* Copy old values in new array */ //XXX: disabled for now, array shoudln't need to be copied, immutable flag seems only present on empty arrays //zend_hash_copy(Z_ARR_P(val), ctag, (copy_ctor_func_t) zval_add_ref); } /* Check if entry has no type */ /* TODO: make depend this code on a parse flag ? */ if (!hasType) { /* Type key */ zend_string *tkey = zend_string_init("type", strlen("type"), 0); /* Type value */ zval tval; /* Check if key is '' */ if (ZSTR_LEN(key) == 0) { /* Set tval value to root */ ZVAL_LONG(&tval, (type = BBCODE_TYPE_ROOT)); /* Grow hasRoot */ hasRoot++; /* Check if key is img */ } else if (strcmp("img", ZSTR_VAL(key)) == 0) { /* Set tval value to single */ ZVAL_LONG(&tval, (type = BBCODE_TYPE_SINGLE)); /* Handle other key as multi */ } else { /* Set tval value to multi */ ZVAL_LONG(&tval, (type = BBCODE_TYPE_MULTI)); } /* Add new type key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), tkey, &tval) != NULL); /* Free type key */ zend_string_release(tkey); /* Set hasType */ hasType = 1; } /* Check for root type */ if (type == BBCODE_TYPE_ROOT) { /* Set root */ root = key; /* Check if has parent */ if (hasParent) { /* Display error about parent entry */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_ROOT cannot have a parent entry", ZSTR_VAL(key), pos); /* Return failure */ return NULL; } /* Check if has open entry */ if (!hasOpen) { /* Key string */ zend_string *mkey = zend_string_init("open", strlen("open"), 0); /* Value */ zval mval; /* Check if tag is empty */ if (ZSTR_LEN(key) == 0) { /* Init new value */ ZVAL_EMPTY_STRING(&mval); /* Non empty tag */ } else { /* Init buffer and length */ buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char)); /* Generate string in */ ZEND_ASSERT(sprintf(buf, "<%s%s>", ZSTR_VAL(key), hasArg?"%s":"") == len); /* Init new value */ ZVAL_STRINGL(&mval, buf, len); /* Free buffer */ free(buf); } /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL); /* Free key */ zend_string_release(mkey); /* Set hasOpen */ hasOpen = 1; } /* Check if has close entry */ if (!hasClose) { /* Key string */ zend_string *mkey = zend_string_init("close", strlen("close"), 0); /* Value */ zval mval; /* Check if tag is empty */ if (ZSTR_LEN(key) == 0) { /* Init new value */ ZVAL_EMPTY_STRING(&mval); /* Non empty tag */ } else { /* Init buffer and length */ buf = (char *)malloc((len = strlen("") + ZSTR_LEN(key))*sizeof(char)); /* Generate string in */ ZEND_ASSERT(sprintf(buf, "", ZSTR_VAL(key)) == len); /* Init new value */ ZVAL_STRINGL(&mval, buf, len); /* Free buffer */ free(buf); } /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL); /* Free key */ zend_string_release(mkey); /* Set hasClose */ hasClose = 1; } /* Check if has not default */ if (!hasDefault) { /* Key string */ zend_string *mkey = zend_string_init("default", strlen("default"), 0); /* Value */ zval mval; /* Set value */ ZVAL_STRING(&mval, "%s"); /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL); /* Free key */ zend_string_release(mkey); /* Set hasDefault */ hasDefault = 1; } /* Compute child here */ if (!hasChild) { /* Child value */ zval cval; /* Init new child val array */ ZVAL_NEW_ARR(&cval); /* Init hash table */ zend_hash_init(Z_ARR(cval), 0, NULL, ZVAL_PTR_DTOR, 0); /* Compute and set child here */ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */ if (bbcode_gen_child(Z_ARR(cval), tag, key) != NULL) { /* Child key string */ zend_string *ckey = zend_string_init("child", strlen("child"), 0); /* Add new child key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL); /* Free type key */ zend_string_release(ckey); /* Set hasChild */ hasChild = 1; /* Child generation reached an error case */ /* XXX: it will trigger error later */ } else { /* Release hash table */ zend_array_destroy(Z_ARR(cval)); } } /* Check for multi type */ } else if (type == BBCODE_TYPE_MULTI) { /* Check if has open entry */ if (!hasOpen) { /* Key string */ zend_string *mkey = zend_string_init("open", strlen("open"), 0); /* Value */ zval mval; /* Check if tag is empty */ if (ZSTR_LEN(key) == 0) { /* Init new value */ ZVAL_EMPTY_STRING(&mval); /* Non empty tag */ } else { /* Init buffer and length */ buf = (char *)malloc((len = strlen("<>") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char)); /* Generate string in */ ZEND_ASSERT(sprintf(buf, "<%s%s>", ZSTR_VAL(key), hasArg?"%s":"") == len); /* Init new value */ ZVAL_STRINGL(&mval, buf, len); /* Free buffer */ free(buf); } /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL); /* Free key */ zend_string_release(mkey); /* Set hasOpen */ hasOpen = 1; } /* Check if has close entry */ if (!hasClose) { /* Key string */ zend_string *mkey = zend_string_init("close", strlen("close"), 0); /* Value */ zval mval; /* Check if tag is empty */ if (ZSTR_LEN(key) == 0) { /* Init new value */ ZVAL_EMPTY_STRING(&mval); /* Non empty tag */ } else { /* Init buffer and length */ buf = (char *)malloc((len = strlen("") + ZSTR_LEN(key))*sizeof(char)); /* Generate string in */ ZEND_ASSERT(sprintf(buf, "", ZSTR_VAL(key)) == len); /* Init new value */ ZVAL_STRINGL(&mval, buf, len); /* Free buffer */ free(buf); } /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL); /* Free key */ zend_string_release(mkey); /* Set hasClose */ hasClose = 1; } /* Check if has not default */ if (!hasDefault) { /* Key string */ zend_string *mkey = zend_string_init("default", strlen("default"), 0); /* Value */ zval mval; /* Set value */ ZVAL_STRING(&mval, "%s"); /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), mkey, &mval) != NULL); /* Free key */ zend_string_release(mkey); /* Set hasDefault */ hasDefault = 1; } /* Compute parent here */ if (!hasParent) { /* Parent key string */ zend_string *pkey = zend_string_init("parent", strlen("parent"), 0); /* Parent value */ zval pval; /* Init new parent val array */ ZVAL_NEW_ARR(&pval); /* Init hash table */ zend_hash_init(Z_ARR(pval), 0, NULL, ZVAL_PTR_DTOR, 0); /* Compute and set parent here */ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */ /* TODO: change this assert to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */ ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval), tag, key) != NULL); /* Add new parent key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), pkey, &pval) != NULL); /* Free type key */ zend_string_release(pkey); /* Set hasParent */ hasParent = 1; } /* Compute child here */ if (!hasChild) { /* Child value */ zval cval; /* Init new child val array */ ZVAL_NEW_ARR(&cval); /* Init hash table */ zend_hash_init(Z_ARR(cval), 0, NULL, ZVAL_PTR_DTOR, 0); /* Compute and set child here */ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */ if (bbcode_gen_child(Z_ARR(cval), tag, key) != NULL) { /* Child key string */ zend_string *ckey = zend_string_init("child", strlen("child"), 0); /* Add new child key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL); /* Free type key */ zend_string_release(ckey); /* Set hasChild */ hasChild = 1; /* Child generation reached an error case */ /* XXX: it will trigger error later */ } else { /* Release hash table */ zend_array_destroy(Z_ARR(cval)); } } /* Check for single type */ } else if (type == BBCODE_TYPE_SINGLE) { /* Check if has open entry */ if (hasOpen) { /* Display error about open entry */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have an open entry", ZSTR_VAL(key), pos); /* Return failure */ return NULL; } /* Check if has close entry */ if (hasClose) { /* Display error about close entry */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a close entry", ZSTR_VAL(key), pos); /* Return failure */ return NULL; } /* Check if has not default */ if (!hasDefault) { /* Key string */ zend_string *skey = zend_string_init("default", strlen("default"), 0); /* Value */ zval sval; /* Check if key is img */ if (strcmp("img", ZSTR_VAL(key)) == 0) { /* Init buffer and length */ buf = (char *)malloc((len = strlen("") + (hasArg?strlen("%s"):0))*sizeof(char)); /* Generate string in */ ZEND_ASSERT(sprintf(buf, "", hasArg?"%s":"") == len); } else { /* Init buffer and length */ buf = (char *)malloc((len = strlen("") + ZSTR_LEN(key) + (hasArg?strlen("%s"):0))*sizeof(char)); /* Generate string in */ ZEND_ASSERT(sprintf(buf, "<%s%s/>", ZSTR_VAL(key), hasArg?"%s":"") == len); } /* Init new value */ ZVAL_STRINGL(&sval, buf, len); /* Free buffer */ free(buf); /* Add new key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), skey, &sval) != NULL); /* Free key */ zend_string_release(skey); /* Set hasDefault */ hasDefault = 1; } /* Check if has child */ if (hasChild) { /* Display error about child entry */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %s[%d] from argument tag of type BBCODE::TYPE_SINGLE cannot have a child entry", ZSTR_VAL(key), pos); /* Return failure */ return NULL; } /* Compute parent here */ if (!hasParent) { /* Parent key string */ zend_string *pkey = zend_string_init("parent", strlen("parent"), 0); /* Parent value */ zval pval; /* Init new parent val array */ ZVAL_NEW_ARR(&pval); /* Init hash table */ zend_hash_init(Z_ARR(pval), 0, NULL, ZVAL_PTR_DTOR, 0); /* Compute and set parent here */ /* XXX: we don't check or trigger error inside this function as these tests are done in current loop */ /* TODO: change this asser to a if() and free pval on error, error will trigger in later loop (code coverage tests to do) */ ZEND_ASSERT(bbcode_gen_parent(Z_ARR(pval), tag, key) != NULL); /* Add new parent key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), pkey, &pval) != NULL); /* Free type key */ zend_string_release(pkey); /* Set hasParent */ hasParent = 1; } } /* Generate open and close pattern for tag with child */ if (hasChild) { /* Child array and value */ zval *child, *ccval; /* Close pattern length */ int len2, cpos2; /* Close pattern buf */ char *buf2; /* Retrieve child array */ /* XXX: child should exists here and be an array */ ZEND_ASSERT(((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) && Z_TYPE_P(child) == IS_ARRAY); /* Init open pattern buffer and length */ /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */ buf = (char *)malloc((len = strlen("/\\[()(?:=([^\\]]*))?\\]/") + zend_array_count(Z_ARR_P(child))*10)*sizeof(char)); /* Init close pattern buffer and length */ /* XXX: lagest bbcode tag length seems to be 7, we guess that 10 is enough to contain every tag and pipe */ buf2 = (char *)malloc((len2 = strlen("/\\[\\/()\\]/") + zend_array_count(Z_ARR_P(child))*10)*sizeof(char)); /* Init cpos and cpos2 */ cpos = 4; cpos2 = 6; /* Assign open pattern begin */ ZEND_ASSERT(memcpy(buf, &"/\\[(", 4) != NULL); /* Assign close pattern begin */ ZEND_ASSERT(memcpy(buf2, &"/\\[\\/(", 6) != NULL); /* Iterate on each val until hash end is reached */ ZEND_HASH_FOREACH_VAL(Z_ARR_P(child), ccval) { /* Check that child val is a string */ /* XXX: ccval should be an array here */ ZEND_ASSERT(Z_TYPE_P(ccval) == IS_STRING); /* Init zval node and type */ zval *znode, *ztype; /* Init zend_long type */ zend_long ltype; /* Retrieve the node */ ZEND_ASSERT((znode = zend_hash_find(tag, Z_STR_P(ccval))) != NULL && Z_TYPE_P(znode) == IS_ARRAY); /* Try to retrieve the type */ //ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG); /* XXX: the type may not yet available here, it is fixed when checking the tag itself later */ if ((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) == NULL || Z_TYPE_P(ztype) != IS_LONG) { /* Check if empty tag */ if (Z_STRLEN_P(ccval) == 0) { /* Set as root */ ltype = BBCODE_TYPE_ROOT; } else if (strcmp("img", Z_STRVAL_P(ccval)) == 0) { /* Set as single */ ltype = BBCODE_TYPE_SINGLE; } else { /* Set as multi */ ltype = BBCODE_TYPE_MULTI; } /* Type zval available and usable */ } else { /* Set type from zval value */ ltype = Z_LVAL_P(ztype); } /* Check if not first string to add */ if (cpos > 4) { /* Set pipe before next tag */ *(buf+cpos) = '|'; /* Move position */ cpos++; /* Check if not single type */ if (ltype != BBCODE_TYPE_SINGLE) { /* Set pipe before next tag */ *(buf2+cpos2) = '|'; /* Move position */ cpos2++; } } /* Check if not single type */ if (ltype != BBCODE_TYPE_SINGLE) { /* Grow buf if necessary */ if (cpos + Z_STRLEN_P(ccval) + 18 > len) { /* We resize buf to current length(cpos), the string size plus eighteen char for trailing pattern */ buf = (char *)realloc(buf, (len = cpos + Z_STRLEN_P(ccval) + 18)*sizeof(char)); } /* Copy string in buf */ ZEND_ASSERT(memcpy(buf+cpos, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL); /* Move cpos */ cpos += Z_STRLEN_P(ccval); /* Grow buf2 if necessary */ if (cpos2 + Z_STRLEN_P(ccval) + 4 > len2) { /* We resize buf2 to current length(cpos2), the string size plus four char for trailing pattern */ buf2 = (char *)realloc(buf2, (len2 = cpos2 + Z_STRLEN_P(ccval) + 4)*sizeof(char)); } /* Copy string in buf2 */ ZEND_ASSERT(memcpy(buf2+cpos2, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL); /* Move cpos2 */ cpos2 += Z_STRLEN_P(ccval); /* Single type case */ } else { /* Grow buf if necessary */ if (cpos + Z_STRLEN_P(ccval) + strlen("(?:\\/)?") + 18 > len) { /* We resize buf to current length(cpos), the string size plus three plus eighteen char for trailing pattern */ buf = (char *)realloc(buf, (len = cpos + Z_STRLEN_P(ccval) + strlen("(?:\\/)?") + 18)*sizeof(char)); } /* Copy string in buf */ ZEND_ASSERT(memcpy(buf+cpos, Z_STRVAL_P(ccval), Z_STRLEN_P(ccval)) != NULL); /* Append pattern in buf */ ZEND_ASSERT(memcpy(buf+cpos+Z_STRLEN_P(ccval), &"(?:\\/)?", strlen("(?:\\/)?")) != NULL); /* Move cpos */ cpos += Z_STRLEN_P(ccval) + strlen("(?:\\/)?"); } } ZEND_HASH_FOREACH_END(); /* Assign open pattern begin */ ZEND_ASSERT(memcpy(buf+cpos, &")(?:=([^\\]]*))?\\]/", 18) != NULL); /* Assign open pattern begin */ ZEND_ASSERT(memcpy(buf2+cpos2, &")\\]/", 4) != NULL); /* Open key string */ zend_string *okey = zend_string_init("opattern", strlen("opattern"), 0); /* Close key string */ zend_string *ckey = zend_string_init("cpattern", strlen("cpattern"), 0); /* Open and close value */ zval oval, cval; /* Set open value */ ZVAL_STRINGL(&oval, buf, cpos + 18); /* Set close value */ ZVAL_STRINGL(&cval, buf2, cpos2 + 4); /* Free buffers */ free(buf); free(buf2); /* Add open key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), okey, &oval) != NULL); /* Add close key */ ZEND_ASSERT(zend_hash_add(Z_ARR_P(val), ckey, &cval) != NULL); /* Free open key */ zend_string_release(okey); /* Free close key */ zend_string_release(ckey); } /* Grow pos */ pos++; } ZEND_HASH_FOREACH_END(); /* Check if not one unique root */ if (hasRoot != 1) { /* Display error about multiple root */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Tag array has %d BBCODE::TYPE_ROOT entry instead of expected unique one", hasRoot); /* Return failure */ return NULL; } /* Duplicate root zend string to avoid it beeing freed */ return zend_string_copy(root); #if 0 /* Return success */ return root; #endif } /* }}} */ /* {{{ static zend_bool *bbcode_check_smiley(HashTable *smiley TSRMLS_DC) { * Check that smiley hash is valid */ static zend_bool bbcode_check_smiley(HashTable *smiley TSRMLS_DC) { /* Key numeric value */ zend_long idx; /* Key string */ zend_string *key; /* Value */ zval *val; /* Key position */ int pos = 0; /* Iterate on each key val until hash end is reached */ ZEND_HASH_FOREACH_KEY_VAL(smiley, idx, key, val) { /* Check that key is a string */ if (key == NULL) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Key %ld[%d] from argument smiley is not an expected string", idx, pos); /* Return failure */ return 0; } /* Check that value exists */ if (val == NULL) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument smiley is NULL instead of expected array", ZSTR_VAL(key), pos); /* Return failure */ return 0; /* Check that value is not an array */ } else if (Z_TYPE_P(val) == IS_ARRAY) { /* Display error about long key */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Value for key %s[%d] from argument smiley is an array instead of expected string", ZSTR_VAL(key), pos); /* Return failure */ return 0; } /* Grow pos */ pos++; } ZEND_HASH_FOREACH_END(); /* Return success */ return 1; } /* }}} */ /* {{{ PHP_METHOD(BBCode, __construct) { */ PHP_METHOD(BBCode, __construct) { /* Init tag hash */ HashTable *tag = NULL; /* Init smiley hash */ HashTable *smiley = NULL; /* Init flag long */ zend_long flag = BBCODE_DEFAULT_FLAG; /* Zend error handling */ zend_error_handling error_handling; /* TODO: set tag array to a default value like this in case of missing/empty tag array : * [ '' => [ ? ] ] * (maybe later) */ /* TODO: init smiley with default list ? what about img storage ? * TODO: use prefix in an ini parameter ? ini_setable ? * TODO: make sure it's not really a global and per-app param ? * (maybe later) */ zend_replace_error_handling(EH_THROW, bbcode_exception_ce, &error_handling); /* Parse parameters */ /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "h|hl", &tag, &smiley, &flag) == FAILURE) { return; }*/ ZEND_PARSE_PARAMETERS_START(1, 3) /* Tag arg is required */ Z_PARAM_ARRAY_HT(tag) /* We switch to optional args */ Z_PARAM_OPTIONAL /* Smiley arg is optional and may be null */ Z_PARAM_ARRAY_HT_EX(smiley, 1, 0) /* Flag arg is optional and may be null */ Z_PARAM_LONG(flag) /* End parameters parse */ ZEND_PARSE_PARAMETERS_END(); /* Retrieve the pointer to this object */ bbcode_object *obj = Z_BBCODE_P(getThis()); /* Verify that the pointer is leading somewhere */ assert(obj != NULL); /* Set flag value */ obj->flag = flag; /* Duplicate hash to avoid modifications on original argument */ obj->tag = zend_array_dup(tag); /* Save tag argument hash in current object */ if ((obj->root = bbcode_check_tag(obj->tag)) == NULL) { /* Restore error handler */ zend_restore_error_handling(&error_handling); /* Release the tag hash */ zend_array_destroy(obj->tag); /* Free the zend object */ //XXX: seems useless, it don't seems possible to null the contructor return value... ZVAL_NULL(getThis() TSRMLS_CC); /* Free the object */ efree(obj); /* Stop here */ return; } /* Check if smiley argument is available */ if (smiley) { /* Duplicate hash to avoid modifications on original argument */ obj->smiley = zend_array_dup(smiley); /* Save smiley argument hash in current object */ if (!bbcode_check_smiley(obj->smiley)) { /* Restore error handler */ zend_restore_error_handling(&error_handling); /* Release the root string */ zend_string_release(obj->root); /* Release the tag hash */ zend_array_destroy(obj->tag); /* Release the smiley hash */ zend_array_destroy(obj->smiley); /* Free the object */ //XXX: seems useless, it don't seems possible to null the contructor return value... ZVAL_NULL(getThis() TSRMLS_CC); /* Free the object */ efree(obj); /* Stop here */ return; } } /* Restore error handler */ zend_restore_error_handling(&error_handling); } /* }}} */ /* {{{ PHP_METHOD(BBCode, __destruct) { * XXX: this function is called by bbcode_destroy function */ PHP_METHOD(BBCode, __destruct) { //XXX: disable this code, we don't need to retrieve obj as we do nothing with userland destructor for now #if 0 /* Retrieve the pointer to this object */ bbcode_object *obj = Z_BBCODE_P(getThis()); /* Verify that the pointer is leading somewhere */ assert(obj != NULL); #endif } /* }}} */ /* */ //[HashTable *ret][zend_string *current][char *str][int offset][int length] static char *bbcode_parse(HashTable *tag, zend_string *cur, char *rs, int *ros, int *rlen, char *str, int len TSRMLS_DC) { /* Check if return string is not initialized or shorter than wanted length */ if (rs == NULL || *rlen - *ros <= 1024) { /* Compute new length */ *rlen = (rs == NULL) ? 2048 : *rlen + 1024; /* Grow the return string */ ZEND_ASSERT((rs = (char *)realloc(rs, (*rlen)*sizeof(char))) != NULL); } //DEBUG: for reminder //str=skip0[r]string0[a=arg0]string1[/a]string2[b]string3[/b][/r]skip1 //len=64 /* Init zval for current node and type */ zval *znode, *ztype; /* Retrieve current tag hash as zval */ ZEND_ASSERT((znode = zend_hash_find(tag, cur)) != NULL && Z_TYPE_P(znode) == IS_ARRAY); /* Retrieve current tag type */ ZEND_ASSERT((ztype = zend_hash_str_find(Z_ARR_P(znode), "type", strlen("type"))) != NULL && Z_TYPE_P(ztype) == IS_LONG); /* Retrieve current tag open */ /* XXX: may be NULL if not present */ zval *zopen = zend_hash_str_find(Z_ARR_P(znode), "open", strlen("open")); /* Retrieve current tag close */ /* XXX: may be NULL if not present */ zval *zclose = zend_hash_str_find(Z_ARR_P(znode), "close", strlen("close")); /* Retrieve current tag default */ /* XXX: may be NULL if not present */ zval *zdefault = zend_hash_str_find(Z_ARR_P(znode), "default", strlen("default")); /* Retrieve current tag arg */ /* XXX: may be NULL if not present */ zval *zarg = zend_hash_str_find(Z_ARR_P(znode), "arg", strlen("arg")); /* The arg zend string */ zend_string *argstr = NULL; /* Check if we have a not root and not empty tag */ if (Z_LVAL_P(ztype) != BBCODE_TYPE_ROOT && strcmp(ZSTR_VAL(cur), "") != 0) { /* Regular expression */ zend_string *regex; /* Open and close pattern */ char *openstr, *closestr, *found; /* Open and close pattern length */ int openlen, closelen; /* Alloc open pattern buffer and set length */ openstr = (char *)malloc(((openlen = strlen("/\\[\\]/") + (zarg == NULL ? strlen("(?:=[^\\]]*)?") : strlen("(?:=([^\\]]*))?")) + ZSTR_LEN(cur)) + 1)*sizeof(char)); /* Alloc close pattern buffer and set length */ closestr = (char *)malloc(((closelen = strlen("[/]") + ZSTR_LEN(cur)) + 1)*sizeof(char)); /* Create open tag string */ ZEND_ASSERT(snprintf(openstr, openlen + 1, "/\\[%s%s\\]/", ZSTR_VAL(cur), (zarg == NULL ? "(?:=[^\\]]*)?" : "(?:=([^\\]]*))?")) == openlen); /* Duplicate open tag in regex */ regex = zend_string_init(openstr, openlen, 0); /* Free the close string */ free(closestr); /* Compiled regular expression */ pcre_cache_entry *pce; /* Array for subpatterns and return value */ zval rv, subpats; /* Init subpats array */ /* XXX: if we don't initialize the array first, some strange memory corruption happen inside php_pcre_match_impl call */ array_init(&subpats); /* Compile regex or get it from cache. */ /* TODO: see if we handle the error here ? (maybe later) * XXX: it seems unlikely any error could be recoverable if pcre function throw something * XXX: special char in tag is already protected earlier * XXX: maybe just check that the pcre error trigger an exception or something uncatchable in all case * XXX: see function pcre_get_compiled_regex_cache in ext/pcre/php_pcre.c +530 */ ZEND_ASSERT((pce = pcre_get_compiled_regex_cache(regex)) != NULL); /* Increase pce refcount */ /* XXX: we use this instead of pce->refcount++; to avoid dereferencing compile error */ php_pcre_pce_incref(pce); /* Call zend pcre implementation */ php_pcre_match_impl(pce, str, len, &rv, &subpats, 0, 0, 0, 0); /* Decrease pce refcount */ /* XXX: we use this instead of pce->refcount--; to avoid dereferencing compile error */ php_pcre_pce_decref(pce); /* Check that we found an occurence */ if (Z_TYPE(rv) == IS_LONG && Z_LVAL(rv) == 1) { /* Init match zval */ zval *match; /* Init the matched tag string */ //char *tstr; /* Test that subpats is an array */ ZEND_ASSERT(Z_TYPE(subpats) == IS_ARRAY); /* Test that subpats members count is right */ ZEND_ASSERT(zend_array_count(Z_ARR(subpats)) == (zarg == NULL ? 1 : 2)); /* Retrieve the matched full tag string */ ZEND_ASSERT((match = zend_hash_index_find(Z_ARR(subpats), 0)) != NULL); /* Test that match is a string */ ZEND_ASSERT(Z_TYPE_P(match) == IS_STRING); /* Find the tag in string */ if ((found = (char *)zend_memnstr(str, Z_STRVAL_P(match), Z_STRLEN_P(match), str+len))) { /* Move str to found position + matched tag string */ str = found + Z_STRLEN_P(match); } /* Check if have and arg to retrieve */ if (zarg != NULL) { /* Retrieve the matched arg string */ ZEND_ASSERT((match = zend_hash_index_find(Z_ARR(subpats), 1)) != NULL); /* Test that match is a string */ ZEND_ASSERT(Z_TYPE_P(match) == IS_STRING); /* Save arg string */ argstr = Z_STR_P(match); } //XXX: code unfinished, these todo are normal, work start here :) //TODO: set the new search position to the current offset plus found subpats[0] string length //TODO: save argument string for appending in return string if zarg is not NULL //TODO: see what happen if we find start tag and not end //TODO: see what happen if we find no start tag and no end //TODO: see what happen if we find no start tag and no end /* No occurence found */ } else { /* XXX: nothing to do ? skip end tag search ??? return to parent level ? */ } /* Destroy subpats array */ zend_array_destroy(Z_ARR(subpats)); /* Create close tag string */ //ZEND_ASSERT(snprintf(closestr, closelen + 1, "/\\[\\/%s\\]/", ZSTR_VAL(cur)) == closelen); ZEND_ASSERT(snprintf(closestr, closelen + 1, "[/%s]", ZSTR_VAL(cur)) == closelen); /* Look for close tag */ /* XXX: watch out, this is a maximum length to consider, the close tag may be present earlier for current leaf */ if ((found = (char *)zend_memnrstr(str, closestr, closelen, str+len))) { /* Reduce len by the size of trailing string since close tag */ len -= len + str - found; } /* Free openstr and closestr */ free(openstr); } printf("str=%.*s\n", len, str); fflush(NULL); return NULL; #if 0 fflush(NULL); printf("str=%s\n", str); #endif /* XXX: debug */ zval ztag; ZVAL_ARR(&ztag, tag); php_debug_zval_dump(&ztag, 0); fflush(NULL); exit(1); php_debug_zval_dump(zopen, 0); php_debug_zval_dump(zclose, 0); php_debug_zval_dump(zdefault, 0); fflush(NULL); exit(1); if (argstr != NULL) { printf("arg=%s[%ld]\n", ZSTR_VAL(argstr), ZSTR_LEN(argstr)); fflush(NULL); exit(1); } //XXX: same here unfinished, code start here :) //TODO: check what happen when we have something like that : [t]s[/t][t]p[/t] //1: compute open and close tag //2: extract end of open tag position and begin of close tag reverse searched in current space //(if we don't find end tag, consider positionning to end of string or rise error ? parse flag involved here) //3: append to return string open tag //4: loop for child tags in subspace and trigger recursion on space between current open and close tags //(loop involved here) //5: duplicate plain strings between each child in return string //6: append the close tag #if 0 /* Check if non empty string (other tag that empty root) */ if (strcmp(ZSTR_VAL(cur), "") != 0) { //TODO: generate search patterns for opening tag and closing tag //TODO: search position of opening tag //TODO: search close tag //TODO: search for child tag /* Non root tag */ } else { //Nothing to do, search offset for child will be 0 and length of search to len //TODO: search current end tag here ? } /* Search next child node */ /**/ //bbcode_search_node(ret, #endif /* Return string by default */ return rs; } /* { { { PHP_METHOD(BBCode, parse) { */ PHP_METHOD(BBCode, parse) { /* Init string to parse */ zend_string *str = NULL; /* Return string */ //TODO: voir si on crée pas une struct pour éviter de trainer 3 arguments char *rs = NULL; /* Return offset and length */ int ros = 0, rlen = 0; /* Parse parameters */ /*if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "S", &str) == FAILURE) { return; }*/ ZEND_PARSE_PARAMETERS_START(1, 1) Z_PARAM_STR(str) ZEND_PARSE_PARAMETERS_END(); /* Retrieve the pointer to this object */ bbcode_object *obj = Z_BBCODE_P(getThis()); /* Init return string and set length to string * 2 plus last null char */ //ZEND_ASSERT((rs = (char *)malloc((rlen = (2*Z_STRLEN(str)) + 1)*sizeof(char)) != NULL); /* Go compute the tree */ bbcode_parse(obj->tag, obj->root, rs, &ros, &rlen, ZSTR_VAL(str), ZSTR_LEN(str)); /* Free return string */ free(rs); #if 0 /* Retrieve root node */ zval *root = zend_hash_find(obj->tag, obj->root); bbcode_ /* Retrieve child array */ /* XXX: child should exists here and be an array */ ZEND_ASSERT(((child = zend_hash_str_find(Z_ARR_P(val), "child", strlen("child"))) != NULL) && Z_TYPE_P(child) == IS_ARRAY); #endif #if BBCODE_DEBUG /* Tag */ zval tag; /* Init new child val array */ ZVAL_ARR(&tag, obj->tag); php_debug_zval_dump(&tag, 0); fflush(NULL); #endif //printf("flag=%ld\n", obj->flag); //printf("die ici: %s +%d\n", __FILE__, __LINE__); //TODO: create a tree for storing result //TODO: apply recursively search of next possible tags (or a closing parent tag ?) regexp and store result inside //TODO: d //TODO: apply the smiley parsing on string /* TODO: the return string */ RETURN_STR(str); } /* }}} */ /* {{{ bbcode_methods[] * * Every BBCode visible function must have an entry in bbcode_methods[]. */ const zend_function_entry bbcode_methods[] = { PHP_ME(BBCode, __construct, bbcode_construct_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(BBCode, __destruct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) PHP_ME(BBCode, parse, bbcode_parse_arginfo, ZEND_ACC_PUBLIC) PHP_FE_END }; /* }}} */ /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(bbcode) { /* If you have INI entries, uncomment these lines REGISTER_INI_ENTRIES(); */ /* Tmp zend class entry*/ zend_class_entry tmp_ce; /* Init BBCode class entry */ INIT_CLASS_ENTRY(tmp_ce, "BBCode", bbcode_methods); /* Register BBCode class entry */ bbcode_ce = zend_register_internal_class(&tmp_ce TSRMLS_CC); bbcode_ce->create_object = bbcode_create; /* Generate const */ BBCODE_DEF(BBCODE_GEN_CONST) /* Init BBCodeException class entry */ INIT_CLASS_ENTRY(tmp_ce, "BBCodeException", NULL); /* Register BBCodeException class entry */ bbcode_exception_ce = zend_register_internal_class_ex(&tmp_ce, zend_exception_get_default(TSRMLS_C) TSRMLS_CC); /* Send success */ return SUCCESS; } /* }}} */ /* {{{ PHP_MSHUTDOWN_FUNCTION */ PHP_MSHUTDOWN_FUNCTION(bbcode) { /* uncomment this line if you have INI entries UNREGISTER_INI_ENTRIES(); */ return SUCCESS; } /* }}} */ /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(bbcode) { php_info_print_table_start(); php_info_print_table_header(2, PHP_BBCODE_NAME " support", "enabled"); php_info_print_table_row(2, "Extension version", PHP_BBCODE_VERSION); php_info_print_table_end(); /* Remove comments if you have entries in php.ini DISPLAY_INI_ENTRIES(); */ } /* }}} */ /* {{{ bbcode_module_entry */ zend_module_entry bbcode_module_entry = { STANDARD_MODULE_HEADER, PHP_BBCODE_NAME, /* extension name */ NULL, /* function list */ PHP_MINIT(bbcode), /* process startup */ PHP_MSHUTDOWN(bbcode), /* process shutdown */ NULL, /* request startup */ NULL, /* request shutdown */ PHP_MINFO(bbcode), /* extension info */ PHP_BBCODE_VERSION, /* extension version */ STANDARD_MODULE_PROPERTIES }; /* }}} */ #ifdef COMPILE_DL_BBCODE #ifdef ZTS ZEND_TSRMLS_CACHE_DEFINE() #endif ZEND_GET_MODULE(bbcode) #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 */