838 lines
27 KiB
C
838 lines
27 KiB
C
/*
|
|
vim:fdm=marker:et:sw=4:ts=4:sts=4:
|
|
*/
|
|
|
|
#include "php.h"
|
|
#include "string.h"
|
|
#include "pcre.h"
|
|
#include "main/php_main.h"
|
|
#include "Zend/zend_compile.h"
|
|
#include "Zend/zend_alloc.h"
|
|
#include "Zend/zend_operators.h"
|
|
#include "Zend/zend_API.h"
|
|
#include "zend_exceptions.h"
|
|
#include "zend_interfaces.h"
|
|
#include "zend_object_handlers.h"
|
|
#include "ext/pcre/php_pcre.h"
|
|
#include "ext/standard/php_string.h"
|
|
#include "php_r3.h"
|
|
#include "r3_functions.h"
|
|
|
|
// #include "r3_persistent.h"
|
|
// #include "php_expandable_mux.h"
|
|
// #include "hash.h"
|
|
|
|
/**
|
|
* new_dst = ht_copy_fun_t(NULL, src);
|
|
*
|
|
* Can be our zval copy function
|
|
*/
|
|
/* {{{ my_copy_zval_ptr */
|
|
zval** my_copy_zval_ptr(zval** dst, const zval** src, int persistent TSRMLS_DC)
|
|
{
|
|
zval* dst_new;
|
|
assert(src != NULL);
|
|
if (!dst) {
|
|
dst = (zval**) pemalloc(sizeof(zval*), persistent);
|
|
}
|
|
CHECK(dst[0] = (zval*) pemalloc(sizeof(zval), persistent));
|
|
CHECK(dst_new = my_copy_zval(*dst, *src, persistent TSRMLS_CC));
|
|
if (dst_new != *dst) {
|
|
*dst = dst_new;
|
|
}
|
|
return dst;
|
|
}
|
|
/* }}} */
|
|
|
|
|
|
|
|
|
|
/* {{{ my_copy_zval */
|
|
zval* my_copy_zval(zval* dst, const zval* src, int persistent TSRMLS_DC)
|
|
{
|
|
zval **tmp;
|
|
assert(dst != NULL);
|
|
assert(src != NULL);
|
|
memcpy(dst, src, sizeof(zval));
|
|
|
|
/* deep copies are refcount(1), but moved up for recursive
|
|
* arrays, which end up being add_ref'd during its copy. */
|
|
Z_SET_REFCOUNT_P(dst, 1);
|
|
Z_UNSET_ISREF_P(dst);
|
|
|
|
switch (src->type & IS_CONSTANT_TYPE_MASK) {
|
|
case IS_RESOURCE:
|
|
php_error(E_ERROR, "Cannot copy resource");
|
|
break;
|
|
case IS_BOOL:
|
|
case IS_LONG:
|
|
case IS_DOUBLE:
|
|
case IS_NULL:
|
|
break;
|
|
|
|
case IS_CONSTANT:
|
|
case IS_STRING:
|
|
if (src->value.str.val) {
|
|
dst->value.str.val = pestrndup(src->value.str.val, src->value.str.len, persistent);
|
|
}
|
|
break;
|
|
|
|
case IS_ARRAY:
|
|
case IS_CONSTANT_ARRAY:
|
|
dst->value.ht = my_copy_hashtable(NULL, src->value.ht, (ht_copy_fun_t) my_copy_zval_ptr, (void*) &tmp, sizeof(zval *), persistent TSRMLS_CC);
|
|
break;
|
|
|
|
// XXX: we don't serialize object.
|
|
case IS_OBJECT:
|
|
php_error(E_ERROR, "Cannot copy Object.");
|
|
break;
|
|
#ifdef ZEND_ENGINE_2_4
|
|
case IS_CALLABLE:
|
|
php_error(E_ERROR, "Cannot copy Callable.");
|
|
// XXX: we don't serialize callbable object.
|
|
break;
|
|
#endif
|
|
default:
|
|
assert(0);
|
|
}
|
|
return dst;
|
|
}
|
|
/* }}} */
|
|
|
|
/* my_zval_copy_ctor_func {{{*/
|
|
void my_zval_copy_ctor_func(zval *zvalue ZEND_FILE_LINE_DC)
|
|
{
|
|
switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) {
|
|
case IS_RESOURCE: {
|
|
TSRMLS_FETCH();
|
|
|
|
zend_list_addref(zvalue->value.lval);
|
|
}
|
|
break;
|
|
case IS_BOOL:
|
|
case IS_LONG:
|
|
case IS_NULL:
|
|
break;
|
|
case IS_CONSTANT:
|
|
case IS_STRING:
|
|
CHECK_ZVAL_STRING_REL(zvalue);
|
|
if (!IS_INTERNED(zvalue->value.str.val)) {
|
|
zvalue->value.str.val = (char *) estrndup_rel(zvalue->value.str.val, zvalue->value.str.len);
|
|
}
|
|
break;
|
|
case IS_ARRAY:
|
|
case IS_CONSTANT_ARRAY: {
|
|
zval *tmp;
|
|
HashTable *original_ht = zvalue->value.ht;
|
|
HashTable *tmp_ht = NULL;
|
|
TSRMLS_FETCH();
|
|
|
|
if (zvalue->value.ht == &EG(symbol_table)) {
|
|
return; /* do nothing */
|
|
}
|
|
ALLOC_HASHTABLE_REL(tmp_ht);
|
|
zend_hash_init(tmp_ht, zend_hash_num_elements(original_ht), NULL, ZVAL_PTR_DTOR, 0);
|
|
zend_hash_copy(tmp_ht, original_ht, (copy_ctor_func_t) my_zval_copy_ctor_func, (void *) &tmp, sizeof(zval *));
|
|
zvalue->value.ht = tmp_ht;
|
|
}
|
|
break;
|
|
case IS_OBJECT:
|
|
{
|
|
TSRMLS_FETCH();
|
|
Z_OBJ_HT_P(zvalue)->add_ref(zvalue TSRMLS_CC);
|
|
}
|
|
break;
|
|
}
|
|
Z_ADDREF_P(zvalue);
|
|
}
|
|
/*}}}*/
|
|
|
|
// my_zval_copy_ctor_persistent_func {{{
|
|
void my_zval_copy_ctor_persistent_func(zval *zvalue ZEND_FILE_LINE_DC)
|
|
{
|
|
/*
|
|
zval *orig_zvalue;
|
|
orig_zvalue = zvalue;
|
|
zvalue = pemalloc(sizeof(zval), 1);
|
|
*zvalue = *zvalue;
|
|
zval_copy_ctor(zvalue);
|
|
Z_SET_REFCOUNT_P(zvalue, 1);
|
|
Z_UNSET_ISREF_P(zvalue);
|
|
// MAKE_COPY_ZVAL(&new_zvalue, zvalue);
|
|
SEPARATE_ZVAL(&zvalue);
|
|
*/
|
|
|
|
switch (Z_TYPE_P(zvalue) & IS_CONSTANT_TYPE_MASK) {
|
|
case IS_RESOURCE:
|
|
case IS_BOOL:
|
|
case IS_LONG:
|
|
case IS_DOUBLE:
|
|
case IS_NULL:
|
|
break;
|
|
|
|
case IS_CONSTANT:
|
|
case IS_STRING:
|
|
CHECK_ZVAL_STRING_REL(zvalue);
|
|
zvalue->value.str.val = (char *) pestrndup(zvalue->value.str.val, zvalue->value.str.len, 1);
|
|
break;
|
|
|
|
case IS_ARRAY:
|
|
case IS_CONSTANT_ARRAY: {
|
|
zval *tmp;
|
|
HashTable *original_ht = zvalue->value.ht;
|
|
HashTable *tmp_ht = NULL;
|
|
TSRMLS_FETCH();
|
|
if (zvalue->value.ht == &EG(symbol_table)) {
|
|
return; /* do nothing */
|
|
}
|
|
tmp_ht = pemalloc(sizeof(HashTable), 1);
|
|
zend_hash_init(tmp_ht, zend_hash_num_elements(original_ht), NULL, ZVAL_PTR_DTOR, 1);
|
|
zend_hash_copy(tmp_ht, original_ht, (copy_ctor_func_t) my_zval_copy_ctor_persistent_func, (void *) &tmp, sizeof(zval *));
|
|
zvalue->value.ht = tmp_ht;
|
|
}
|
|
break;
|
|
case IS_OBJECT:
|
|
{
|
|
TSRMLS_FETCH();
|
|
Z_OBJ_HT_P(zvalue)->add_ref(zvalue TSRMLS_CC);
|
|
}
|
|
break;
|
|
}
|
|
Z_SET_REFCOUNT_P(zvalue, 1);
|
|
}
|
|
// }}}
|
|
|
|
int _r3_store_mux(char *name, zval * mux TSRMLS_DC)
|
|
{
|
|
zend_rsrc_list_entry new_le, *le;
|
|
|
|
// variables for copying mux object properties
|
|
char *id_key, *expand_key;
|
|
int status, id_key_len, expand_key_len;
|
|
|
|
id_key_len = spprintf(&id_key, 0, "mux_id_%s", name);
|
|
expand_key_len = spprintf(&expand_key, 0, "mux_expand_%s", name);
|
|
|
|
|
|
// Z_ADDREF_P(mux);
|
|
|
|
// make the hash table persistent
|
|
zval *prop, *tmp;
|
|
HashTable *routes, *static_routes;
|
|
|
|
prop = zend_read_property(ce_r3_mux, mux, "routes", sizeof("routes")-1, 1 TSRMLS_CC);
|
|
|
|
routes = zend_hash_clone_persistent( Z_ARRVAL_P(prop) TSRMLS_CC);
|
|
if ( ! routes ) {
|
|
php_error(E_ERROR, "Can not clone HashTable");
|
|
return FAILURE;
|
|
}
|
|
r3_persistent_store( name, "routes", le_mux_hash_table, (void*) routes TSRMLS_CC);
|
|
return SUCCESS;
|
|
|
|
|
|
prop = zend_read_property(ce_r3_mux, mux, "staticRoutes", sizeof("staticRoutes")-1, 1 TSRMLS_CC);
|
|
static_routes = zend_hash_clone_persistent( Z_ARRVAL_P(prop) TSRMLS_CC);
|
|
if ( ! static_routes ) {
|
|
php_error(E_ERROR, "Can not clone HashTable");
|
|
return FAILURE;
|
|
}
|
|
r3_persistent_store(name, "static_routes", le_mux_hash_table, (void *) static_routes TSRMLS_CC) ;
|
|
|
|
|
|
// copy ID
|
|
/*
|
|
prop = zend_read_property(ce_r3_mux, mux, "id", sizeof("id")-1, 1 TSRMLS_CC);
|
|
tmp = pemalloc(sizeof(zval), 1);
|
|
INIT_ZVAL(tmp);
|
|
Z_TYPE_P(tmp) = IS_LONG;
|
|
Z_LVAL_P(tmp) = Z_LVAL_P(prop);
|
|
Z_SET_REFCOUNT_P(tmp, 1);
|
|
r3_persistent_store( name, "id", (void*) tmp TSRMLS_CC);
|
|
*/
|
|
|
|
// We cannot copy un-expandable mux object because we don't support recursively copy for Mux object.
|
|
prop = zend_read_property(ce_r3_mux, mux, "expand", sizeof("expand")-1, 1 TSRMLS_CC);
|
|
if ( ! Z_BVAL_P(prop) ) {
|
|
php_error(E_ERROR, "We cannot copy un-expandable mux object because we don't support recursively copy for Mux object.");
|
|
}
|
|
efree(id_key);
|
|
efree(expand_key);
|
|
return SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
* Fetch mux related properties (hash tables) from EG(persistent_list) hash table and
|
|
* rebless a new Mux object with these properties.
|
|
*
|
|
* @return Mux object
|
|
*/
|
|
zval * _r3_fetch_mux(char *name TSRMLS_DC)
|
|
{
|
|
zval *z_id, *z_routes, *z_static_routes, *z_submux, *z_routes_by_id, *tmp;
|
|
HashTable *routes_hash;
|
|
HashTable *static_routes_hash;
|
|
|
|
// fetch related hash to this mux object.
|
|
routes_hash = (HashTable*) r3_persistent_fetch(name, "routes" TSRMLS_CC);
|
|
if ( ! routes_hash ) {
|
|
return NULL;
|
|
}
|
|
|
|
static_routes_hash = (HashTable*) r3_persistent_fetch(name, "static_routes" TSRMLS_CC);
|
|
if ( ! static_routes_hash ) {
|
|
return NULL;
|
|
}
|
|
|
|
z_id = (zval*) r3_persistent_fetch(name, "id" TSRMLS_CC);
|
|
MAKE_STD_ZVAL(z_routes);
|
|
MAKE_STD_ZVAL(z_static_routes);
|
|
MAKE_STD_ZVAL(z_routes_by_id);
|
|
MAKE_STD_ZVAL(z_submux);
|
|
|
|
Z_TYPE_P(z_routes) = IS_ARRAY;
|
|
Z_TYPE_P(z_static_routes) = IS_ARRAY;
|
|
array_init(z_routes_by_id);
|
|
array_init(z_submux);
|
|
|
|
// we need to clone hash table deeply because when Mux object returned to userspace, it will be freed.
|
|
Z_ARRVAL_P(z_routes) = zend_hash_clone(routes_hash TSRMLS_CC);
|
|
Z_ARRVAL_P(z_static_routes) = zend_hash_clone(static_routes_hash TSRMLS_CC);
|
|
|
|
// create new object and return to userspace.
|
|
zval *new_mux = NULL;
|
|
ALLOC_INIT_ZVAL(new_mux);
|
|
object_init_ex(new_mux, ce_r3_mux);
|
|
|
|
// We don't need __construct because we assign the property by ourself.
|
|
// CALL_METHOD(Mux, __construct, new_mux, new_mux);
|
|
Z_SET_REFCOUNT_P(new_mux, 1);
|
|
|
|
|
|
|
|
if ( z_id ) {
|
|
Z_ADDREF_P(z_id);
|
|
zend_update_property_long(ce_r3_mux, new_mux, "id" , sizeof("id")-1, Z_LVAL_P(z_id) TSRMLS_CC);
|
|
}
|
|
// persistent mux should always be expanded. (no recursive structure)
|
|
zend_update_property_bool(ce_r3_mux , new_mux , "expand" , sizeof("expand")-1 , 1 TSRMLS_CC);
|
|
zend_update_property(ce_r3_mux , new_mux , "routes" , sizeof("routes")-1 , z_routes TSRMLS_CC);
|
|
zend_update_property(ce_r3_mux , new_mux , "staticRoutes" , sizeof("staticRoutes")-1 , z_static_routes TSRMLS_CC);
|
|
|
|
zend_update_property(ce_r3_mux, new_mux, "routesById", sizeof("routesById")-1, z_routes_by_id TSRMLS_CC);
|
|
zend_update_property(ce_r3_mux, new_mux, "submux", sizeof("submux")-1, z_submux TSRMLS_CC);
|
|
return new_mux;
|
|
}
|
|
|
|
|
|
int mux_loader(char *path, zval *result TSRMLS_DC)
|
|
{
|
|
zend_file_handle file_handle;
|
|
zend_op_array *op_array;
|
|
char realpath[MAXPATHLEN];
|
|
|
|
if (!VCWD_REALPATH(path, realpath)) {
|
|
return FAILURE;
|
|
}
|
|
|
|
file_handle.filename = path;
|
|
file_handle.free_filename = 0;
|
|
file_handle.type = ZEND_HANDLE_FILENAME;
|
|
file_handle.opened_path = NULL;
|
|
file_handle.handle.fp = NULL;
|
|
|
|
op_array = zend_compile_file(&file_handle, ZEND_INCLUDE TSRMLS_CC);
|
|
|
|
|
|
if (op_array && file_handle.handle.stream.handle) {
|
|
int dummy = 1;
|
|
|
|
if (!file_handle.opened_path) {
|
|
file_handle.opened_path = path;
|
|
}
|
|
|
|
zend_hash_add(&EG(included_files), file_handle.opened_path, strlen(file_handle.opened_path)+1, (void *)&dummy, sizeof(int), NULL);
|
|
}
|
|
zend_destroy_file_handle(&file_handle TSRMLS_CC);
|
|
|
|
if (op_array) {
|
|
zval *local_retval_ptr = NULL;
|
|
|
|
R3_STORE_EG_ENVIRON();
|
|
EG(return_value_ptr_ptr) = &local_retval_ptr;
|
|
EG(active_op_array) = op_array;
|
|
|
|
#if ((PHP_MAJOR_VERSION == 5) && (PHP_MINOR_VERSION > 2)) || (PHP_MAJOR_VERSION > 5)
|
|
if (!EG(active_symbol_table)) {
|
|
zend_rebuild_symbol_table(TSRMLS_C);
|
|
}
|
|
#endif
|
|
zend_execute(op_array TSRMLS_CC);
|
|
|
|
destroy_op_array(op_array TSRMLS_CC);
|
|
efree(op_array);
|
|
|
|
if (!EG(exception)) {
|
|
if (local_retval_ptr) {
|
|
if ( result ) {
|
|
COPY_PZVAL_TO_ZVAL(*result, local_retval_ptr);
|
|
} else {
|
|
zval_ptr_dtor(EG(return_value_ptr_ptr));
|
|
}
|
|
}
|
|
}
|
|
R3_RESTORE_EG_ENVIRON();
|
|
return SUCCESS;
|
|
}
|
|
|
|
return FAILURE;
|
|
}
|
|
|
|
|
|
/*
|
|
* r3_compile(array $routes, string $path);
|
|
*/
|
|
PHP_FUNCTION(r3_match)
|
|
{
|
|
zval *z_routes;
|
|
char *path;
|
|
int path_len;
|
|
|
|
/* parse parameters */
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "as",
|
|
&z_routes,
|
|
&path, &path_len ) == FAILURE) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
zval *z_route;
|
|
z_route = php_r3_match(z_routes, path, path_len TSRMLS_CC);
|
|
if ( z_route != NULL ) {
|
|
*return_value = *z_route;
|
|
zval_copy_ctor(return_value);
|
|
return;
|
|
}
|
|
RETURN_NULL();
|
|
}
|
|
|
|
PHP_FUNCTION(r3_store_mux)
|
|
{
|
|
zval *mux;
|
|
char *name;
|
|
int name_len;
|
|
|
|
/* parse parameters */
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name, &name_len, &mux ) == FAILURE) {
|
|
RETURN_FALSE;
|
|
}
|
|
if ( _r3_store_mux(name, mux TSRMLS_CC) == SUCCESS ) {
|
|
RETURN_TRUE;
|
|
}
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
|
|
PHP_FUNCTION(r3_persistent_dispatch)
|
|
{
|
|
char *ns, *filename, *path;
|
|
int ns_len, filename_len, path_len;
|
|
zval *mux = NULL;
|
|
zval *route = NULL;
|
|
zval *z_path = NULL;
|
|
|
|
/* parse parameters */
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sss", &ns, &ns_len, &filename, &filename_len, &path, &path_len) == FAILURE) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
mux = _r3_fetch_mux(ns TSRMLS_CC);
|
|
if ( mux == NULL ) {
|
|
ALLOC_INIT_ZVAL(mux);
|
|
if ( mux_loader(filename, mux TSRMLS_CC) == FAILURE ) {
|
|
php_error(E_ERROR, "Can not load Mux object from %s", filename);
|
|
}
|
|
|
|
// TODO: compile mux and sort routes
|
|
if ( _r3_store_mux(ns, mux TSRMLS_CC) == FAILURE ) {
|
|
php_error(E_ERROR, "Can not store Mux object from %s", filename);
|
|
}
|
|
}
|
|
|
|
ALLOC_INIT_ZVAL(z_path);
|
|
ZVAL_STRINGL(z_path, path ,path_len, 1); // no copy
|
|
|
|
// XXX: pass return_value to the method call, so we don't need to copy
|
|
route = call_mux_method(mux, "dispatch" , sizeof("dispatch"), 1 , z_path, NULL, NULL TSRMLS_CC);
|
|
zval_ptr_dtor(&z_path);
|
|
|
|
if ( route ) {
|
|
*return_value = *route;
|
|
zval_copy_ctor(return_value);
|
|
return;
|
|
}
|
|
// route not found
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
|
|
|
|
PHP_FUNCTION(r3_delete_mux)
|
|
{
|
|
char *name, *persistent_key;
|
|
int name_len, persistent_key_len;
|
|
zend_rsrc_list_entry *le;
|
|
|
|
/* parse parameters */
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
|
|
&name, &name_len) == FAILURE) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
persistent_key_len = spprintf(&persistent_key, 0, "mux_%s", name);
|
|
|
|
if ( zend_hash_find(&EG(persistent_list), persistent_key, persistent_key_len + 1, (void**) &le) == SUCCESS ) {
|
|
zval_ptr_dtor((zval**) &le->ptr);
|
|
zend_hash_del(&EG(persistent_list), persistent_key, persistent_key_len + 1);
|
|
efree(persistent_key);
|
|
RETURN_TRUE;
|
|
}
|
|
efree(persistent_key);
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
PHP_FUNCTION(r3_fetch_mux)
|
|
{
|
|
char *name;
|
|
int name_len;
|
|
zval * mux;
|
|
|
|
/* parse parameters */
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
|
|
&name, &name_len) == FAILURE) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
mux = _r3_fetch_mux(name TSRMLS_CC);
|
|
if ( mux ) {
|
|
*return_value = *mux;
|
|
zval_copy_ctor(return_value);
|
|
return;
|
|
}
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
PHP_FUNCTION(r3_sort_routes)
|
|
{
|
|
zval *a;
|
|
zval *b;
|
|
|
|
/* parse parameters */
|
|
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz",
|
|
&a,
|
|
&b ) == FAILURE) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
zval **a_pcre;
|
|
zval **a_pattern;
|
|
zval **a_compiled_pattern;
|
|
zval **a_options;
|
|
|
|
zval **b_pcre;
|
|
zval **b_pattern;
|
|
zval **b_compiled_pattern;
|
|
zval **b_options;
|
|
|
|
|
|
zend_hash_index_find( Z_ARRVAL_P(a) , 0, (void**)&a_pcre);
|
|
zend_hash_index_find( Z_ARRVAL_P(b) , 0, (void**)&b_pcre);
|
|
|
|
zend_hash_index_find( Z_ARRVAL_P(a) , 1, (void**)&a_pattern);
|
|
zend_hash_index_find( Z_ARRVAL_P(b) , 1, (void**)&b_pattern);
|
|
|
|
zend_hash_index_find( Z_ARRVAL_P(a) , 3, (void**)&a_options);
|
|
zend_hash_index_find( Z_ARRVAL_P(b) , 3, (void**)&b_options);
|
|
|
|
|
|
// return strlen($a[3]['compiled']) > strlen($b[3]['compiled']);
|
|
if ( Z_BVAL_PP(a_pcre) && Z_BVAL_PP(b_pcre) ) {
|
|
zend_hash_quick_find( Z_ARRVAL_PP(a_options) , "compiled", strlen("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&a_compiled_pattern);
|
|
zend_hash_quick_find( Z_ARRVAL_PP(b_options) , "compiled", strlen("compiled"), zend_inline_hash_func(ZEND_STRS("compiled")), (void**)&b_compiled_pattern);
|
|
|
|
int a_len = Z_STRLEN_PP(a_compiled_pattern);
|
|
int b_len = Z_STRLEN_PP(b_compiled_pattern);
|
|
if ( a_len == b_len ) {
|
|
RETURN_LONG(0);
|
|
} else if ( a_len > b_len ) {
|
|
RETURN_LONG(-1);
|
|
} else {
|
|
RETURN_LONG(1);
|
|
}
|
|
}
|
|
else if ( Z_BVAL_PP(a_pcre) ) {
|
|
RETURN_LONG(-1);
|
|
}
|
|
else if ( Z_BVAL_PP(b_pcre) ) {
|
|
RETURN_LONG(1);
|
|
}
|
|
|
|
int a_len = Z_STRLEN_PP(a_pattern);
|
|
int b_len = Z_STRLEN_PP(b_pattern);
|
|
|
|
if ( a_len == b_len ) {
|
|
RETURN_LONG(0);
|
|
}
|
|
else if ( a_len > b_len ) {
|
|
RETURN_LONG(-1);
|
|
}
|
|
else {
|
|
RETURN_LONG(1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// int zend_hash_has_key( )
|
|
//
|
|
inline zval * php_r3_match(zval *z_routes, char *path, int path_len TSRMLS_DC) {
|
|
int current_request_method = 0;
|
|
int current_https = 0;
|
|
zval * current_http_host = NULL;
|
|
|
|
HashTable *server_vars_hash = fetch_server_vars_hash(TSRMLS_C);
|
|
if ( server_vars_hash ) {
|
|
current_request_method = get_current_request_method_const(server_vars_hash TSRMLS_CC);
|
|
current_https = get_current_https(server_vars_hash TSRMLS_CC);
|
|
current_http_host = get_current_http_host(server_vars_hash TSRMLS_CC);
|
|
}
|
|
|
|
HashPosition z_routes_pointer;
|
|
|
|
// for iterating routes
|
|
zval **z_route_pp;
|
|
|
|
zval **z_is_pcre_pp; // route[0]
|
|
zval **z_pattern_pp; // route[1]
|
|
zval **z_callback_pp; // callback @ route[2]
|
|
zval **z_route_options_pp; // route[3]
|
|
|
|
pcre_cache_entry *pce; /* Compiled regular expression */
|
|
|
|
zval *pcre_ret = NULL;
|
|
zval *pcre_subpats = NULL; /* Array for subpatterns */
|
|
|
|
HashTable * z_routes_hash = Z_ARRVAL_P(z_routes);
|
|
|
|
ALLOC_INIT_ZVAL(pcre_ret); // this is required.
|
|
ALLOC_INIT_ZVAL(pcre_subpats); // also required
|
|
|
|
for(zend_hash_internal_pointer_reset_ex(z_routes_hash, &z_routes_pointer);
|
|
zend_hash_get_current_data_ex(z_routes_hash, (void**) &z_route_pp, &z_routes_pointer) == SUCCESS;
|
|
zend_hash_move_forward_ex(z_routes_hash, &z_routes_pointer))
|
|
{
|
|
zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 0, (void**) &z_is_pcre_pp);
|
|
zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 1, (void**) &z_pattern_pp);
|
|
|
|
if ( Z_BVAL_PP(z_is_pcre_pp) ) {
|
|
/* Compile regex or get it from cache. */
|
|
if ((pce = pcre_get_compiled_regex_cache(Z_STRVAL_PP(z_pattern_pp), Z_STRLEN_PP(z_pattern_pp) TSRMLS_CC)) == NULL) {
|
|
zend_throw_exception(zend_exception_get_default(TSRMLS_C), "PCRE pattern compile failed.", 0 TSRMLS_CC);
|
|
return NULL;
|
|
}
|
|
|
|
php_pcre_match_impl(pce, path, path_len, pcre_ret, pcre_subpats, 0, 0, 0, 0 TSRMLS_CC);
|
|
|
|
// not matched ?
|
|
if ( ! Z_BVAL_P(pcre_ret) ) {
|
|
continue;
|
|
}
|
|
|
|
// tell garbage collector to collect it, we need to use pcre_subpats later.
|
|
|
|
// check conditions only when route option is provided
|
|
if ( zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 3, (void**) &z_route_options_pp) == SUCCESS ) {
|
|
if ( zend_hash_num_elements(Z_ARRVAL_PP(z_route_options_pp)) ) {
|
|
if ( 0 == validate_request_method( z_route_options_pp, current_request_method TSRMLS_CC) ) {
|
|
continue;
|
|
}
|
|
if ( 0 == validate_https( z_route_options_pp, current_https TSRMLS_CC) ) {
|
|
continue;
|
|
}
|
|
if ( 0 == validate_domain( z_route_options_pp, current_http_host TSRMLS_CC) ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( Z_TYPE_P(pcre_subpats) == IS_NULL ) {
|
|
array_init(pcre_subpats);
|
|
}
|
|
|
|
Z_ADDREF_P(pcre_subpats);
|
|
add_assoc_zval(*z_route_options_pp , "vars" , pcre_subpats);
|
|
return *z_route_pp;
|
|
// Apply "default" value to "vars"
|
|
/*
|
|
foreach( $route['variables'] as $k ) {
|
|
if( isset($regs[$k]) ) {
|
|
$route['vars'][ $k ] = $regs[$k];
|
|
} else {
|
|
$route['vars'][ $k ] = $route['default'][ $k ];
|
|
}
|
|
}
|
|
*/
|
|
/*
|
|
zval **z_route_default;
|
|
zval **z_route_subpat_val;
|
|
if ( zend_hash_find(z_route_options_pp, "default", sizeof("default"), (void**) &z_route_default ) == FAILURE ) {
|
|
HashPosition default_pointer;
|
|
HashTable *default_hash;
|
|
|
|
variables_hash = Z_ARRVAL_PP(z_variables);
|
|
|
|
// foreach variables as var, check if url contains variable or we should apply default value
|
|
for(zend_hash_internal_pointer_reset_ex(variables_hash, &variables_pointer);
|
|
zend_hash_get_current_data_ex(variables_hash, (void**) &z_var_name, &variables_pointer) == SUCCESS;
|
|
zend_hash_move_forward_ex(variables_hash, &variables_pointer))
|
|
{
|
|
}
|
|
// if ( zend_hash_find(z_route_default, "default", sizeof("default"), (void**) &z_route_default ) == FAILURE ) {
|
|
}
|
|
*/
|
|
} else {
|
|
// normal string comparison
|
|
// pattern-prefix match
|
|
zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 2, (void**) &z_callback_pp);
|
|
if ( ( Z_TYPE_PP(z_callback_pp) == IS_LONG && strncmp(Z_STRVAL_PP( z_pattern_pp ), path, Z_STRLEN_PP(z_pattern_pp) ) == 0 )
|
|
|| strcmp(Z_STRVAL_PP( z_pattern_pp ), path ) == 0 )
|
|
{
|
|
|
|
// check conditions
|
|
if ( zend_hash_index_find( Z_ARRVAL_PP(z_route_pp), 3, (void**) &z_route_options_pp) == SUCCESS ) {
|
|
if ( zend_hash_num_elements(Z_ARRVAL_PP(z_route_options_pp)) ) {
|
|
if ( 0 == validate_request_method( z_route_options_pp, current_request_method TSRMLS_CC) ) {
|
|
continue;
|
|
}
|
|
if ( 0 == validate_https( z_route_options_pp, current_https TSRMLS_CC) ) {
|
|
continue;
|
|
}
|
|
if ( 0 == validate_domain( z_route_options_pp, current_http_host TSRMLS_CC) ) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
// we didn't use the pcre variables
|
|
zval_ptr_dtor(&pcre_subpats);
|
|
zval_ptr_dtor(&pcre_ret);
|
|
return *z_route_pp;
|
|
}
|
|
}
|
|
}
|
|
zval_ptr_dtor(&pcre_subpats);
|
|
zval_ptr_dtor(&pcre_ret);
|
|
return NULL;
|
|
}
|
|
|
|
inline HashTable * fetch_server_vars_hash(TSRMLS_D) {
|
|
zval **z_server_hash = NULL;
|
|
if ( zend_hash_quick_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), zend_inline_hash_func(ZEND_STRS("_SERVER")), (void **) &z_server_hash) == SUCCESS ) {
|
|
return Z_ARRVAL_PP(z_server_hash);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
inline zval * fetch_server_var(HashTable * server_hash, char *key , int key_len TSRMLS_DC) {
|
|
zval **rv;
|
|
if ( zend_hash_find(server_hash, key, key_len, (void **) &rv) == SUCCESS ) {
|
|
return *rv;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
inline zval * get_current_remote_addr(HashTable * server_vars_hash TSRMLS_DC) {
|
|
// REMOTE_ADDR
|
|
return fetch_server_var(server_vars_hash, "REMOTE_ADDR", sizeof("REMOTE_ADDR") TSRMLS_CC);
|
|
}
|
|
|
|
inline zval * get_current_http_host(HashTable * server_vars_hash TSRMLS_DC) {
|
|
return fetch_server_var(server_vars_hash, "HTTP_HOST", sizeof("HTTP_HOST") TSRMLS_CC);
|
|
}
|
|
|
|
inline zval * get_current_request_uri(HashTable * server_vars_hash TSRMLS_DC) {
|
|
return fetch_server_var(server_vars_hash, "REQUEST_URI", sizeof("REQUEST_URI") TSRMLS_CC);
|
|
}
|
|
|
|
inline int get_current_https(HashTable * server_vars_hash TSRMLS_DC) {
|
|
zval *https = fetch_server_var(server_vars_hash, "HTTPS", sizeof("HTTPS") TSRMLS_CC);
|
|
if ( https && Z_BVAL_P(https) ) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline zval * get_current_request_method(HashTable * server_vars_hash TSRMLS_DC) {
|
|
return fetch_server_var(server_vars_hash, "REQUEST_METHOD", sizeof("REQUEST_METHOD") TSRMLS_CC);
|
|
}
|
|
|
|
/* get request method type in constant value. {{{
|
|
*/
|
|
inline int get_current_request_method_const(HashTable * server_vars_hash TSRMLS_DC) {
|
|
char *c_request_method;
|
|
zval *z_request_method = get_current_request_method(server_vars_hash TSRMLS_CC);
|
|
if ( z_request_method ) {
|
|
c_request_method = Z_STRVAL_P(z_request_method);
|
|
if ( strncmp("GET", c_request_method , sizeof("GET") ) == 0 ) {
|
|
return REQ_METHOD_GET;
|
|
} else if ( strncmp("POST", c_request_method , sizeof("POST") ) == 0 ) {
|
|
return REQ_METHOD_POST;
|
|
} else if ( strncmp("PUT" , c_request_method , sizeof("PUT") ) == 0 ) {
|
|
return REQ_METHOD_PUT;
|
|
} else if ( strncmp("DELETE", c_request_method, sizeof("DELETE") ) == 0 ) {
|
|
return REQ_METHOD_DELETE;
|
|
} else if ( strncmp("HEAD", c_request_method, sizeof("HEAD") ) == 0 ) {
|
|
return REQ_METHOD_HEAD;
|
|
} else if ( strncmp("PATCH", c_request_method, sizeof("PATCH") ) == 0 ) {
|
|
return REQ_METHOD_HEAD;
|
|
} else if ( strncmp("OPTIONS", c_request_method, sizeof("OPTIONS") ) == 0 ) {
|
|
return REQ_METHOD_OPTIONS;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
// }}}
|
|
|
|
inline int validate_https(zval **z_route_options_pp, int https TSRMLS_DC)
|
|
{
|
|
zval **z_route_secure = NULL;
|
|
if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options_pp) , "secure", sizeof("secure"), zend_inline_hash_func(ZEND_STRS("secure")), (void**) &z_route_secure ) == SUCCESS ) {
|
|
// check HTTPS flag
|
|
if ( https && ! Z_BVAL_PP(z_route_secure) ) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
inline int validate_domain(zval **z_route_options_pp, zval * http_host TSRMLS_DC)
|
|
{
|
|
zval **z_route_domain = NULL;
|
|
if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options_pp) , "domain", sizeof("domain"), zend_inline_hash_func(ZEND_STRS("domain")), (void**) &z_route_domain ) == SUCCESS ) {
|
|
// check HTTP_HOST from $_SERVER
|
|
if ( strncmp(Z_STRVAL_PP(z_route_domain), Z_STRVAL_P(http_host), Z_STRLEN_PP(z_route_domain) ) != 0 ) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
inline int validate_request_method(zval **z_route_options_pp, int current_request_method TSRMLS_DC)
|
|
{
|
|
zval **z_route_method = NULL;
|
|
if ( zend_hash_quick_find( Z_ARRVAL_PP(z_route_options_pp) , "method", sizeof("method"), zend_inline_hash_func(ZEND_STRS("method")), (void**) &z_route_method ) == SUCCESS ) {
|
|
if ( Z_TYPE_PP(z_route_method) == IS_LONG && Z_LVAL_PP(z_route_method) != current_request_method ) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|