1 /*
2   +----------------------------------------------------------------------+
3   | Yet Another Framework                                                |
4   +----------------------------------------------------------------------+
5   | This source file is subject to version 3.01 of the PHP license,      |
6   | that is bundled with this package in the file LICENSE, and is        |
7   | available through the world-wide-web at the following url:           |
8   | http://www.php.net/license/3_01.txt                                  |
9   | If you did not receive a copy of the PHP license and are unable to   |
10   | obtain it through the world-wide-web, please send a note to          |
11   | license@php.net so we can mail you a copy immediately.               |
12   +----------------------------------------------------------------------+
13   | Author: Xinchen Hui  <laruence@php.net>                              |
14   +----------------------------------------------------------------------+
15  */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "php.h"
22 #include "ext/pcre/php_pcre.h"
23 #include "Zend/zend_smart_str.h" /* for smart_str */
24 #include "Zend/zend_interfaces.h" /* for zend_class_serialize_deny */
25 
26 #include "php_yaf.h"
27 #include "yaf_namespace.h"
28 #include "yaf_exception.h"
29 #include "yaf_request.h"
30 
31 #include "yaf_router.h"
32 #include "routes/yaf_route_interface.h"
33 #include "routes/yaf_route_regex.h"
34 #include "ext/standard/php_string.h"
35 
36 zend_class_entry *yaf_route_regex_ce;
37 static zend_object_handlers yaf_route_regex_obj_handlers;
38 
39 /** {{{ ARG_INFO
40  */
41 ZEND_BEGIN_ARG_INFO_EX(yaf_route_regex_construct_arginfo, 0, 0, 2)
42 	ZEND_ARG_INFO(0, match)
43 	ZEND_ARG_ARRAY_INFO(0, route, 0)
44 	ZEND_ARG_ARRAY_INFO(0, map, 1)
45 	ZEND_ARG_ARRAY_INFO(0, verify, 1)
46 	ZEND_ARG_INFO(0, reverse)
ZEND_END_ARG_INFO()47 ZEND_END_ARG_INFO()
48 
49 ZEND_BEGIN_ARG_INFO_EX(yaf_route_regex_match_arginfo, 0, 0, 1)
50 	ZEND_ARG_INFO(0, uri)
51 ZEND_END_ARG_INFO()
52 /* }}} */
53 
54 static HashTable *yaf_route_regex_get_properties(yaf_object *object) /* {{{ */ {
55 	zval rv;
56 	HashTable *ht;
57 	yaf_route_regex_object *regex = (yaf_route_regex_object*)(yaf_strip_obj(object));
58 
59 	if (!regex->properties) {
60 		ALLOC_HASHTABLE(regex->properties);
61 		zend_hash_init(regex->properties, 8, NULL, ZVAL_PTR_DTOR, 0);
62 
63 		ht = regex->properties;
64 		ZVAL_STR_COPY(&rv, regex->match);
65 		zend_hash_str_add(ht, "match:protected", sizeof("match:protected") - 1, &rv);
66 
67 		ZVAL_ARR(&rv, regex->router);
68 		GC_ADDREF(regex->router);
69 		zend_hash_str_add(ht, "route:protected", sizeof("route:protected") - 1, &rv);
70 
71 		if (regex->map) {
72 			ZVAL_ARR(&rv, regex->map);
73 			GC_ADDREF(regex->map);
74 		} else {
75 			ZVAL_NULL(&rv);
76 		}
77 		zend_hash_str_add(ht, "map:protected", sizeof("map:protected") - 1, &rv);
78 
79 		if (regex->verify) {
80 			ZVAL_ARR(&rv, regex->verify);
81 			GC_ADDREF(regex->verify);
82 		} else {
83 			ZVAL_NULL(&rv);
84 		}
85 		zend_hash_str_add(ht, "verify:protected", sizeof("verify:protected") - 1, &rv);
86 
87 		if (regex->reverse) {
88 			ZVAL_STR_COPY(&rv, regex->reverse);
89 		} else {
90 			ZVAL_NULL(&rv);
91 		}
92 		zend_hash_str_add(ht, "reverse:protected", sizeof("reverse:protected") - 1, &rv);
93 	}
94 
95 	return regex->properties;
96 }
97 /* }}} */
98 
yaf_route_regex_new(zend_class_entry * ce)99 static zend_object *yaf_route_regex_new(zend_class_entry *ce) /* {{{ */ {
100 	yaf_route_regex_object *regex = emalloc(sizeof(yaf_route_regex_object));
101 
102 	zend_object_std_init(&regex->std, ce);
103 
104 	regex->std.handlers = &yaf_route_regex_obj_handlers;
105 
106 	regex->match = NULL;
107 	regex->router = NULL;
108 	regex->router = NULL;
109 	regex->verify = NULL;
110 	regex->properties = NULL;
111 
112 	return &regex->std;
113 }
114 /* }}} */
115 
yaf_route_regex_object_free(zend_object * object)116 static void yaf_route_regex_object_free(zend_object *object) /* {{{ */ {
117 	yaf_route_regex_object *regex = (yaf_route_regex_object*)object;
118 
119 	if (regex->match) {
120 		zend_string_release(regex->match);
121 	}
122 
123 	if (regex->reverse) {
124 		zend_string_release(regex->reverse);
125 	}
126 
127 	if (regex->router) {
128 		if (!(GC_FLAGS(regex->router) & IS_ARRAY_IMMUTABLE) && GC_DELREF(regex->router) == 0) {
129 			GC_REMOVE_FROM_BUFFER(regex->router);
130 			zend_array_destroy(regex->router);
131 		}
132 	}
133 
134 	if (regex->map) {
135 		if (!(GC_FLAGS(regex->map) & IS_ARRAY_IMMUTABLE) && GC_DELREF(regex->map) == 0) {
136 			GC_REMOVE_FROM_BUFFER(regex->map);
137 			zend_array_destroy(regex->map);
138 		}
139 	}
140 
141 	if (regex->verify) {
142 		if (!(GC_FLAGS(regex->verify) & IS_ARRAY_IMMUTABLE) && GC_DELREF(regex->verify) == 0) {
143 			GC_REMOVE_FROM_BUFFER(regex->verify);
144 			zend_array_destroy(regex->verify);
145 		}
146 	}
147 
148 	if (regex->properties) {
149 		if (GC_DELREF(regex->properties) == 0) {
150 			GC_REMOVE_FROM_BUFFER(regex->properties);
151 			zend_array_destroy(regex->properties);
152 		}
153 	}
154 
155 	zend_object_std_dtor(&regex->std);
156 }
157 /* }}} */
158 
yaf_route_regex_init(yaf_route_regex_object * regex,zend_string * match,zval * router,zval * map,zval * verify,zend_string * reverse)159 static void yaf_route_regex_init(yaf_route_regex_object *regex, zend_string *match, zval *router, zval *map, zval *verify, zend_string *reverse) /* {{{ */ {
160 	regex->match = zend_string_copy(match);
161 
162 	if (router) {
163 		regex->router = zend_array_dup(Z_ARRVAL_P(router));
164 	} else {
165 		regex->router = NULL;
166 	}
167 
168 	if (map) {
169 		regex->map = zend_array_dup(Z_ARRVAL_P(map));
170 	} else {
171 		regex->map = NULL;
172 	}
173 
174 	if (verify) {
175 		regex->verify = zend_array_dup(Z_ARRVAL_P(verify));
176 	} else {
177 		regex->verify = NULL;
178 	}
179 
180 	if (reverse) {
181 		regex->reverse = zend_string_copy(reverse);
182 	} else {
183 		regex->reverse = NULL;
184 	}
185 }
186 /* }}} */
187 
yaf_route_regex_instance(yaf_route_t * route,zend_string * match,zval * router,zval * map,zval * verify,zend_string * reverse)188 void yaf_route_regex_instance(yaf_route_t *route, zend_string *match, zval *router, zval *map, zval *verify, zend_string *reverse) /* {{{ */ {
189 	zend_object *regex = yaf_route_regex_new(yaf_route_regex_ce);
190 
191 	yaf_route_regex_init((yaf_route_regex_object*)regex, match, router, map, verify, reverse);
192 
193 	ZVAL_OBJ(route, regex);
194 }
195 /* }}} */
196 
yaf_route_regex_match(yaf_route_regex_object * regex,const char * uri,size_t len,zval * ret)197 static int yaf_route_regex_match(yaf_route_regex_object *regex, const char *uri, size_t len, zval *ret) /* {{{ */ {
198 	pcre_cache_entry *pce_regexp;
199 
200 	if (UNEXPECTED(len == 0)) {
201 		return 0;
202 	}
203 
204 	ZEND_ASSERT(regex->match);
205 
206 	if ((pce_regexp = pcre_get_compiled_regex_cache(regex->match)) == NULL) {
207 		return 0;
208 	} else {
209 		zval matches, subparts;
210 
211 		ZVAL_NULL(&subparts);
212 #if PHP_VERSION_ID < 70400
213 		php_pcre_match_impl(pce_regexp, (char*)uri, len, &matches, &subparts /* subpats */,
214 				0/* global */, 0/* ZEND_NUM_ARGS() >= 4 */, 0/*flags PREG_OFFSET_CAPTURE*/, 0/* start_offset */);
215 #else
216 		{
217 			zend_string *tmp = zend_string_init(uri, len, 0);
218 			php_pcre_match_impl(pce_regexp, tmp, &matches, &subparts /* subpats */,
219 					0/* global */, 0/* ZEND_NUM_ARGS() >= 4 */, 0/*flags PREG_OFFSET_CAPTURE*/, 0/* start_offset */);
220 			zend_string_release(tmp);
221 		}
222 #endif
223 
224 		if (!zend_hash_num_elements(Z_ARRVAL(subparts))) {
225 			zval_ptr_dtor(&subparts);
226 			return 0;
227 		} else {
228 			zval *name, *pzval;
229 			zend_string	*key = NULL;
230 			zend_ulong idx = 0;
231 			HashTable *ht;
232 
233 			array_init(ret);
234 
235 			ht = Z_ARRVAL(subparts);
236 			ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, pzval) {
237 				if (key) {
238 					Z_TRY_ADDREF_P(pzval);
239 					zend_hash_update(Z_ARRVAL_P(ret), key, pzval);
240 				} else {
241 					if (regex->map && (name = zend_hash_index_find(regex->map, idx)) && Z_TYPE_P(name) == IS_STRING) {
242 						Z_TRY_ADDREF_P(pzval);
243 						zend_hash_update(Z_ARRVAL_P(ret), Z_STR_P(name), pzval);
244 					}
245 				}
246 			} ZEND_HASH_FOREACH_END();
247 
248 			zval_ptr_dtor(&subparts);
249 			return 1;
250 		}
251 	}
252 }
253 /* }}} */
254 
yaf_route_regex_route(yaf_route_t * route,yaf_request_t * req)255 int yaf_route_regex_route(yaf_route_t *route, yaf_request_t *req) /* {{{ */ {
256 	zval args;
257 	const char *req_uri;
258 	size_t req_uri_len;
259 	yaf_request_object *request = Z_YAFREQUESTOBJ_P(req);
260 	yaf_route_regex_object *regex = Z_YAFROUTEREGEXOBJ_P(route);
261 
262 	if (request->base_uri) {
263 		req_uri = yaf_request_strip_base_uri(request->uri, request->base_uri, &req_uri_len);
264 	} else {
265 		req_uri = ZSTR_VAL(request->uri);
266 		req_uri_len = ZSTR_LEN(request->uri);
267 	}
268 
269 	if (!yaf_route_regex_match(regex, req_uri, req_uri_len, &args)) {
270 		return 0;
271 	} else {
272 		zval *module, *controller, *action;
273 
274 		ZEND_ASSERT(regex->router);
275 		if ((module = zend_hash_str_find(regex->router, ZEND_STRL("module"))) && IS_STRING == Z_TYPE_P(module)) {
276 			if (Z_STRVAL_P(module)[0] != ':') {
277 				yaf_request_set_module(request, Z_STR_P(module));
278 			} else {
279 				zval *m;
280 				if ((m = zend_hash_str_find(Z_ARRVAL(args), Z_STRVAL_P(module) + 1, Z_STRLEN_P(module) - 1)) && IS_STRING == Z_TYPE_P(m)) {
281 					yaf_request_set_module(request, Z_STR_P(m));
282 				}
283 			}
284 		}
285 
286 		if ((controller = zend_hash_str_find(regex->router, ZEND_STRL("controller"))) && IS_STRING == Z_TYPE_P(controller)) {
287 			if (Z_STRVAL_P(controller)[0] != ':') {
288 				yaf_request_set_controller(request, Z_STR_P(controller));
289 			} else {
290 				zval *c;
291 				if ((c = zend_hash_str_find(Z_ARRVAL(args), Z_STRVAL_P(controller) + 1, Z_STRLEN_P(controller) - 1)) && IS_STRING == Z_TYPE_P(c)) {
292 					yaf_request_set_controller(request, Z_STR_P(c));
293 				}
294 			}
295 		}
296 
297 		if ((action = zend_hash_str_find(regex->router, ZEND_STRL("action"))) && IS_STRING == Z_TYPE_P(action)) {
298 			if (Z_STRVAL_P(action)[0] != ':') {
299 				yaf_request_set_action(request, Z_STR_P(action));
300 			} else {
301 				zval *a;
302 				if ((a = zend_hash_str_find(Z_ARRVAL(args), Z_STRVAL_P(action) + 1, Z_STRLEN_P(action) - 1)) && IS_STRING == Z_TYPE_P(a)) {
303 					yaf_request_set_action(request, Z_STR_P(a));
304 				}
305 			}
306 		}
307 
308 		yaf_request_set_params_multi(request, &args);
309 		zval_ptr_dtor(&args);
310 	}
311 
312 	return 1;
313 }
314 /* }}} */
315 
yaf_route_regex_assemble(yaf_route_regex_object * regex,zval * info,zval * query)316 zend_string * yaf_route_regex_assemble(yaf_route_regex_object *regex, zval *info, zval *query) /* {{{ */ {
317 	zval *zv;
318 	zend_string *val;
319 	zend_string *uri;
320 	zend_string *inter;
321 	smart_str query_str = {0};
322 
323 	if (UNEXPECTED(regex->reverse == NULL)) {
324 		return NULL;
325 	}
326 
327 	uri = zend_string_copy(regex->reverse);
328 	if ((zv = zend_hash_str_find(Z_ARRVAL_P(info), ZEND_STRL(YAF_ROUTE_ASSEMBLE_MOUDLE_FORMAT))) != NULL) {
329 		val = zval_get_string(zv);
330 		inter = php_str_to_str(ZSTR_VAL(regex->reverse), ZSTR_LEN(regex->reverse),
331 				ZEND_STRL(YAF_ROUTE_ASSEMBLE_MOUDLE_FORMAT), ZSTR_VAL(val), ZSTR_LEN(val));
332 		zend_string_release(val);
333 		zend_string_release(uri);
334 		uri = inter;
335 	}
336 
337 	if ((zv = zend_hash_str_find(Z_ARRVAL_P(info), ZEND_STRL(YAF_ROUTE_ASSEMBLE_CONTROLLER_FORMAT))) != NULL) {
338 		val = zval_get_string(zv);
339 		inter = php_str_to_str(ZSTR_VAL(uri), ZSTR_LEN(uri),
340 				ZEND_STRL(YAF_ROUTE_ASSEMBLE_CONTROLLER_FORMAT), ZSTR_VAL(val), ZSTR_LEN(val));
341 		zend_string_release(val);
342 		zend_string_release(uri);
343 		uri = inter;
344 	}
345 
346 	if ((zv = zend_hash_str_find(Z_ARRVAL_P(info), ZEND_STRL(YAF_ROUTE_ASSEMBLE_ACTION_FORMAT))) != NULL) {
347 		val = zval_get_string(zv);
348 		inter = php_str_to_str(ZSTR_VAL(uri), ZSTR_LEN(uri),
349 				ZEND_STRL(YAF_ROUTE_ASSEMBLE_ACTION_FORMAT), Z_STRVAL_P(zv), Z_STRLEN_P(zv));
350 		zend_string_release(val);
351 		zend_string_release(uri);
352 		uri = inter;
353 	}
354 
355 	if (query && IS_ARRAY == Z_TYPE_P(query)) {
356 		zend_string *key;
357 		HashTable *ht = Z_ARRVAL_P(query);
358 
359 		smart_str_appendc(&query_str, '?');
360 		ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, zv) {
361 			if (key) {
362 				val = zval_get_string(zv);
363 				smart_str_appendl(&query_str, ZSTR_VAL(key), ZSTR_LEN(key));
364 				smart_str_appendc(&query_str, '=');
365 				smart_str_appendl(&query_str, Z_STRVAL_P(zv), Z_STRLEN_P(zv));
366 				smart_str_appendc(&query_str, '&');
367 				zend_string_release(val);
368 			}
369 		} ZEND_HASH_FOREACH_END();
370 	}
371 
372 	if (query_str.s) {
373 		size_t orig_len = ZSTR_LEN(uri);
374 		ZSTR_LEN(query_str.s)--; /* get rid of the tail & */
375 		smart_str_0(&query_str);
376 		uri = zend_string_realloc(uri, ZSTR_LEN(uri) + ZSTR_LEN(query_str.s), 0);
377 		memcpy(ZSTR_VAL(uri) + orig_len, ZSTR_VAL(query_str.s), ZSTR_LEN(query_str.s));
378 		ZSTR_VAL(uri)[ZSTR_LEN(uri)] = '\0';
379 		smart_str_free(&query_str);
380 	}
381 
382 	return uri;
383 }
384 /** }}} */
385 
386 /** {{{ proto public Yaf_Route_Regex::route(string $uri)
387  */
PHP_METHOD(yaf_route_regex,route)388 PHP_METHOD(yaf_route_regex, route) {
389 	yaf_request_t *request;
390 
391 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &request, yaf_request_ce) == FAILURE) {
392 		return;
393 	}
394 
395 	RETURN_BOOL(yaf_route_regex_route(getThis(), request));
396 }
397 /** }}} */
398 
399 /** {{{ proto public Yaf_Route_Regex::match(string $uri)
400  */
PHP_METHOD(yaf_route_regex,match)401 PHP_METHOD(yaf_route_regex, match) {
402 	zend_string *uri;
403 
404 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &uri) == FAILURE) {
405 		return;
406 	}
407 
408 	if (ZSTR_LEN(uri) == 0) {
409 		RETURN_FALSE;
410 	}
411 
412 	if (!yaf_route_regex_match(Z_YAFROUTEREGEXOBJ_P(getThis()), ZSTR_VAL(uri), ZSTR_LEN(uri), return_value)) {
413 		RETURN_FALSE;
414 	}
415 }
416 /** }}} */
417 
418 /** {{{ proto public Yaf_Route_Regex::__construct(string $match, array $route, array $map = NULL, array $verify = NULL, string reverse = NULL)
419  */
PHP_METHOD(yaf_route_regex,__construct)420 PHP_METHOD(yaf_route_regex, __construct) {
421 	zend_string *match;
422 	zend_string *reverse = NULL;
423 	zval *route, *map = NULL, *verify = NULL;
424 
425 	if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "Sa|a!a!S!", &match, &route, &map, &verify, &reverse) ==  FAILURE) {
426 		return;
427 	}
428 
429 	yaf_route_regex_init(Z_YAFROUTEREGEXOBJ_P(getThis()), match, route, map, verify, reverse);
430 }
431 /** }}} */
432 
433 /** {{{ proto public Yaf_Route_regex::assemble(array $info[, array $query = NULL])
434  */
PHP_METHOD(yaf_route_regex,assemble)435 PHP_METHOD(yaf_route_regex, assemble) {
436 	zval *info, *query = NULL;
437 	zend_string *str;
438 
439 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|a", &info, &query) == FAILURE) {
440 		return;
441 	}
442 
443 	if ((str = yaf_route_regex_assemble(Z_YAFROUTEREGEXOBJ_P(getThis()), info, query)) != NULL) {
444 		RETURN_STR(str);
445 	}
446 
447 	RETURN_NULL();
448 }
449 /* }}} */
450 
451 /** {{{ yaf_route_regex_methods
452  */
453 zend_function_entry yaf_route_regex_methods[] = {
454 	PHP_ME(yaf_route_regex, __construct, yaf_route_regex_construct_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
455 	PHP_ME(yaf_route_regex, match, yaf_route_regex_match_arginfo, ZEND_ACC_PUBLIC)
456 	PHP_ME(yaf_route_regex, route, yaf_route_route_arginfo, ZEND_ACC_PUBLIC)
457 	PHP_ME(yaf_route_regex, assemble, yaf_route_assemble_arginfo, ZEND_ACC_PUBLIC)
458     {NULL, NULL, NULL}
459 };
460 /* }}} */
461 
462 /** {{{ YAF_STARTUP_FUNCTION
463  */
YAF_STARTUP_FUNCTION(route_regex)464 YAF_STARTUP_FUNCTION(route_regex) {
465 	zend_class_entry ce;
466 	YAF_INIT_CLASS_ENTRY(ce, "Yaf_Route_Regex", "Yaf\\Route\\Regex", yaf_route_regex_methods);
467 	yaf_route_regex_ce = zend_register_internal_class(&ce);
468 	yaf_route_regex_ce->create_object = yaf_route_regex_new;
469 	yaf_route_regex_ce->ce_flags |= ZEND_ACC_FINAL;
470 	yaf_route_regex_ce->serialize = zend_class_serialize_deny;
471 	yaf_route_regex_ce->unserialize = zend_class_unserialize_deny;
472 
473 	zend_class_implements(yaf_route_regex_ce, 1, yaf_route_ce);
474 
475 	memcpy(&yaf_route_regex_obj_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
476 	yaf_route_regex_obj_handlers.free_obj = yaf_route_regex_object_free;
477 	yaf_route_regex_obj_handlers.get_gc = yaf_fake_get_gc;
478 	yaf_route_regex_obj_handlers.clone_obj = NULL;
479 	yaf_route_regex_obj_handlers.get_properties = yaf_route_regex_get_properties;
480 
481 	return SUCCESS;
482 }
483 /* }}} */
484 
485 /*
486  * Local variables:
487  * tab-width: 4
488  * c-basic-offset: 4
489  * End:
490  * vim600: noet sw=4 ts=4 fdm=marker
491  * vim<600: noet sw=4 ts=4
492  */
493 
494