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" /* for pcre */
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_rewrite.h"
34 
35 #include "ext/standard/php_string.h"
36 
37 zend_class_entry *yaf_route_rewrite_ce;
38 static zend_object_handlers yaf_route_rewrite_obj_handlers;
39 
40 /** {{{ ARG_INFO
41  */
42 ZEND_BEGIN_ARG_INFO_EX(yaf_route_rewrite_construct_arginfo, 0, 0, 2)
43 	ZEND_ARG_INFO(0, match)
44 	ZEND_ARG_ARRAY_INFO(0, route, 0)
45 	ZEND_ARG_ARRAY_INFO(0, verify, 1)
ZEND_END_ARG_INFO()46 ZEND_END_ARG_INFO()
47 
48 ZEND_BEGIN_ARG_INFO_EX(yaf_route_rewrite_match_arginfo, 0, 0, 1)
49 	ZEND_ARG_INFO(0, uri)
50 ZEND_END_ARG_INFO()
51 /* }}} */
52 
53 static HashTable *yaf_route_rewrite_get_properties(yaf_object *object) /* {{{ */ {
54 	zval rv;
55 	HashTable *ht;
56 	yaf_route_rewrite_object *rewrite = (yaf_route_rewrite_object*)(yaf_strip_obj(object));
57 
58 	if (!rewrite->properties) {
59 		ALLOC_HASHTABLE(rewrite->properties);
60 		zend_hash_init(rewrite->properties, 4, NULL, ZVAL_PTR_DTOR, 0);
61 
62 		ht = rewrite->properties;
63 		ZVAL_STR_COPY(&rv, rewrite->match);
64 		zend_hash_str_add(ht, "match:protected", sizeof("match:protected") - 1, &rv);
65 
66 		ZVAL_ARR(&rv, rewrite->router);
67 		Z_TRY_ADDREF(rv);
68 		zend_hash_str_add(ht, "route:protected", sizeof("route:protected") - 1, &rv);
69 
70 		if (rewrite->verify) {
71 			ZVAL_ARR(&rv, rewrite->verify);
72 			Z_TRY_ADDREF(rv);
73 		} else {
74 			ZVAL_NULL(&rv);
75 		}
76 		zend_hash_str_add(ht, "verify:protected", sizeof("verify:protected") - 1, &rv);
77 	}
78 
79 	return rewrite->properties;
80 }
81 /* }}} */
82 
yaf_route_rewrite_new(zend_class_entry * ce)83 static zend_object *yaf_route_rewrite_new(zend_class_entry *ce) /* {{{ */ {
84 	yaf_route_rewrite_object *rewrite = emalloc(sizeof(yaf_route_rewrite_object));
85 
86 	zend_object_std_init(&rewrite->std, ce);
87 	rewrite->std.handlers = &yaf_route_rewrite_obj_handlers;
88 
89 	rewrite->match = NULL;
90 	rewrite->router = NULL;
91 	rewrite->verify = NULL;
92 	rewrite->properties = NULL;
93 
94 	return &rewrite->std;
95 }
96 /* }}} */
97 
yaf_route_rewrite_object_free(zend_object * object)98 static void yaf_route_rewrite_object_free(zend_object *object) /* {{{ */ {
99 	yaf_route_rewrite_object *rewrite = (yaf_route_rewrite_object*)object;
100 
101 	if (rewrite->match) {
102 		zend_string_release(rewrite->match);
103 	}
104 
105 	if (rewrite->router) {
106 		if ((GC_DELREF(rewrite->router) == 0)) {
107 			GC_REMOVE_FROM_BUFFER(rewrite->router);
108 			zend_array_destroy(rewrite->router);
109 		}
110 	}
111 
112 	if (rewrite->verify) {
113 		if ((GC_DELREF(rewrite->verify) == 0)) {
114 			GC_REMOVE_FROM_BUFFER(rewrite->verify);
115 			zend_array_destroy(rewrite->verify);
116 		}
117 	}
118 
119 	if (rewrite->properties) {
120 		if ((GC_DELREF(rewrite->properties) == 0)) {
121 			GC_REMOVE_FROM_BUFFER(rewrite->properties);
122 			zend_array_destroy(rewrite->properties);
123 		}
124 	}
125 
126 	zend_object_std_dtor(&rewrite->std);
127 }
128 /* }}} */
129 
yaf_route_rewrite_init(yaf_route_rewrite_object * rewrite,zend_string * match,zval * router,zval * verify)130 static void yaf_route_rewrite_init(yaf_route_rewrite_object *rewrite, zend_string *match, zval *router, zval *verify) /* {{{ */ {
131 	rewrite->match = zend_string_copy(match);
132 
133 	if (router) {
134 		rewrite->router = zend_array_dup(Z_ARRVAL_P(router));
135 	} else {
136 		rewrite->router = NULL;
137 	}
138 
139 	if (verify) {
140 		rewrite->verify = zend_array_dup(Z_ARRVAL_P(verify));
141 	} else {
142 		rewrite->verify = NULL;
143 	}
144 }
145 /* }}} */
146 
yaf_route_rewrite_instance(yaf_route_t * route,zend_string * match,zval * router,zval * verify)147 void yaf_route_rewrite_instance(yaf_route_t *route, zend_string *match, zval *router, zval *verify) /* {{{ */ {
148 	zend_object *rewrite = yaf_route_rewrite_new(yaf_route_rewrite_ce);
149 
150 	yaf_route_rewrite_init((yaf_route_rewrite_object*)rewrite, match, router, verify);
151 
152 	ZVAL_OBJ(route, rewrite);
153 }
154 /* }}} */
155 
yaf_route_rewrite_match(yaf_route_rewrite_object * rewrite,const char * uri,size_t len,zval * ret)156 static int yaf_route_rewrite_match(yaf_route_rewrite_object *rewrite, const char *uri, size_t len, zval *ret) /* {{{ */ {
157 	char *pos, *m;
158 	uint32_t l;
159 	pcre_cache_entry *pce_regexp;
160 	smart_str pattern = {0};
161 
162 
163 	ZEND_ASSERT(rewrite->match);
164 
165 	smart_str_appendc(&pattern, YAF_ROUTE_REGEX_DILIMITER);
166 	smart_str_appendc(&pattern, '^');
167 
168 	m = ZSTR_VAL(rewrite->match);
169 	l = ZSTR_LEN(rewrite->match);
170 	while (l) {
171 		if (*m == '*') {
172 			smart_str_appendl(&pattern, "(?P<__yaf_route_rest>.*)", sizeof("(?P<__yaf_route_rest>.*)") -1);
173 			break;
174 		} else {
175 			uint32_t len;
176 			pos = memchr(m, YAF_ROUTER_URL_DELIMIETER, l);
177 			if (pos) {
178 				len = pos - m;
179 				l -= len;
180 			} else {
181 				len = l;
182 				l = 0;
183 			}
184 			if (*m == ':') {
185 				smart_str_appendl(&pattern, "(?P<", sizeof("(?P<") -1 );
186 				smart_str_appendl(&pattern, m + 1, len - 1);
187 				smart_str_appendl(&pattern, ">[^", sizeof(">[^") - 1);
188 				smart_str_appendc(&pattern, YAF_ROUTER_URL_DELIMIETER);
189 				smart_str_appendl(&pattern, "]+)", sizeof("]+)") - 1);
190 			} else {
191 				smart_str_appendl(&pattern, m, len);
192 			}
193 			if (pos) {
194 				smart_str_appendc(&pattern, YAF_ROUTER_URL_DELIMIETER);
195 				smart_str_appendc(&pattern, '+');
196 				m = ++pos;
197 				l--;
198 			}
199 		}
200 	}
201 
202 	smart_str_appendc(&pattern, YAF_ROUTE_REGEX_DILIMITER);
203 	smart_str_appendc(&pattern, 'i');
204 	smart_str_0(&pattern);
205 	pce_regexp = pcre_get_compiled_regex_cache(pattern.s);
206 	smart_str_free(&pattern);
207 
208 	if (pce_regexp) {
209 		zval matches, subparts;
210 
211 		smart_str_free(&pattern);
212 
213 		ZVAL_NULL(&subparts);
214 
215 #if PHP_VERSION_ID < 70400
216 		php_pcre_match_impl(pce_regexp, (char*)uri, len, &matches, &subparts /* subpats */,
217 				0/* global */, 0/* ZEND_NUM_ARGS() >= 4 */, 0/*flags PREG_OFFSET_CAPTURE*/, 0/* start_offset */);
218 #else
219 		{
220 			zend_string *tmp = zend_string_init(uri, len, 0);
221 			php_pcre_match_impl(pce_regexp, tmp, &matches, &subparts /* subpats */,
222 					0/* global */, 0/* ZEND_NUM_ARGS() >= 4 */, 0/*flags PREG_OFFSET_CAPTURE*/, 0/* start_offset */);
223 			zend_string_release(tmp);
224 		}
225 #endif
226 
227 		if (!zend_hash_num_elements(Z_ARRVAL(subparts))) {
228 			zval_ptr_dtor(&subparts);
229 			return 0;
230 		} else {
231 			zval *pzval;
232 			zend_string *key;
233 			HashTable *ht;
234 
235 			array_init(ret);
236 
237 			ht = Z_ARRVAL(subparts);
238 			ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, pzval) {
239 				if (key) {
240 					if (zend_string_equals_literal(key, "__yaf_route_rest")) {
241 						zval params;
242 						yaf_router_parse_parameters(Z_STRVAL_P(pzval), Z_STRLEN_P(pzval), &params);
243 						zend_hash_copy(Z_ARRVAL_P(ret), Z_ARRVAL(params), (copy_ctor_func_t) zval_add_ref);
244 						zval_ptr_dtor(&params);
245 					} else {
246 						Z_ADDREF_P(pzval);
247 						zend_hash_update(Z_ARRVAL_P(ret), key, pzval);
248 					}
249 				}
250 			} ZEND_HASH_FOREACH_END();
251 
252 			zval_ptr_dtor(&subparts);
253 			return 1;
254 		}
255 	}
256 
257 	return 0;
258 }
259 /* }}} */
260 
yaf_route_rewrite_route(yaf_route_t * route,yaf_request_t * req)261 int yaf_route_rewrite_route(yaf_route_t *route, yaf_request_t *req) /* {{{ */ {
262 	zval args;
263 	const char *req_uri;
264 	size_t req_uri_len;
265 	yaf_request_object *request = Z_YAFREQUESTOBJ_P(req);
266 	yaf_route_rewrite_object *rewrite = Z_YAFROUTEREWRITEOBJ_P(route);
267 
268 	if (request->base_uri) {
269 		req_uri = yaf_request_strip_base_uri(request->uri, request->base_uri, &req_uri_len);
270 	} else {
271 		req_uri = ZSTR_VAL(request->uri);
272 		req_uri_len = ZSTR_LEN(request->uri);
273 	}
274 
275 	if (UNEXPECTED(req_uri_len == 0)) {
276 		return 0;
277 	}
278 
279 	if (EXPECTED(yaf_route_rewrite_match(rewrite, req_uri, req_uri_len, &args))) {
280 		zval *module, *controller, *action;
281 
282 		ZEND_ASSERT(rewrite->router);
283 		if ((module = zend_hash_str_find(rewrite->router, ZEND_STRL("module"))) != NULL && IS_STRING == Z_TYPE_P(module)) {
284 			if (Z_STRVAL_P(module)[0] != ':') {
285 				yaf_request_set_module(request, Z_STR_P(module));
286 			} else {
287 				zval *m;
288 				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)) {
289 					yaf_request_set_module(request, Z_STR_P(m));
290 				}
291 			}
292 		}
293 
294 		if ((controller = zend_hash_str_find(rewrite->router, ZEND_STRL("controller"))) && IS_STRING == Z_TYPE_P(controller)) {
295 			if (Z_STRVAL_P(controller)[0] != ':') {
296 				yaf_request_set_controller(request, Z_STR_P(controller));
297 			} else {
298 				zval *c;
299 				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)) {
300 					yaf_request_set_controller(request, Z_STR_P(c));
301 				}
302 			}
303 		}
304 
305 		if ((action = zend_hash_str_find(rewrite->router, ZEND_STRL("action"))) && IS_STRING == Z_TYPE_P(action)) {
306 			if (Z_STRVAL_P(action)[0] != ':') {
307 				yaf_request_set_action(request, Z_STR_P(action));
308 			} else {
309 				zval *a;
310 				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)) {
311 					yaf_request_set_action(request, Z_STR_P(a));
312 				}
313 			}
314 		}
315 
316 		yaf_request_set_params_multi(request, &args);
317 		zval_ptr_dtor(&args);
318 
319 		return 1;
320 	}
321 
322 	return 0;
323 }
324 /* }}} */
325 
yaf_route_rewrite_assemble(yaf_route_rewrite_object * rewrite,zval * info,zval * query)326 zend_string *yaf_route_rewrite_assemble(yaf_route_rewrite_object *rewrite, zval *info, zval *query) /* {{{ */ {
327 	zval pidents, *zv;
328 	char *seg, *pmatch, *ptrptr;
329 	zend_string *key, *inter, *uri, *val;
330 	size_t seg_len;
331 	smart_str query_str = {0};
332 	smart_str wildcard = {0};
333 	char token[2] = {YAF_ROUTER_URL_DELIMIETER, 0};
334 
335 	array_init(&pidents);
336 
337 	uri = zend_string_copy(rewrite->match);
338 	pmatch = estrndup(ZSTR_VAL(rewrite->match), ZSTR_LEN(rewrite->match));
339 	zend_hash_copy(Z_ARRVAL(pidents), Z_ARRVAL_P(info), (copy_ctor_func_t) zval_add_ref);
340 
341 	seg = php_strtok_r(pmatch, token, &ptrptr);
342 	while (seg) {
343 		seg_len = strlen(seg);
344 		if (seg_len) {
345 			if (*(seg) == '*') {
346 				ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL(pidents), key, zv) {
347 					if (key) {
348 						if (IS_STRING == Z_TYPE_P(zv)) {
349 							smart_str_appendl(&wildcard, ZSTR_VAL(key) + 1, ZSTR_LEN(key) - 1);
350 							smart_str_appendc(&wildcard, YAF_ROUTER_URL_DELIMIETER);
351 							smart_str_appendl(&wildcard, Z_STRVAL_P(zv), Z_STRLEN_P(zv));
352 							smart_str_appendc(&wildcard, YAF_ROUTER_URL_DELIMIETER);
353 						}
354 					}
355 				} ZEND_HASH_FOREACH_END();
356 				smart_str_0(&wildcard);
357 				inter = php_str_to_str(ZSTR_VAL(uri), ZSTR_LEN(uri),
358 						"*", 1, ZSTR_VAL(wildcard.s), ZSTR_LEN(wildcard.s));
359 				zend_string_release(uri);
360 				uri = inter;
361 				break;
362 			}
363 
364 			if (*(seg) == ':') {
365 				if ((zv = zend_hash_str_find(Z_ARRVAL_P(info), seg, seg_len)) != NULL) {
366 					val = zval_get_string(zv);
367 					inter = php_str_to_str(ZSTR_VAL(uri),
368 							ZSTR_LEN(uri), seg, seg_len, ZSTR_VAL(val), ZSTR_LEN(val));
369 					zend_string_release(val);
370 					zend_string_release(uri);
371 					zend_hash_str_del(Z_ARRVAL(pidents), seg, seg_len);
372 					uri = inter;
373 				}
374 			}
375 		}
376 		seg = php_strtok_r(NULL, token, &ptrptr);
377 	}
378 
379 	smart_str_free(&wildcard);
380 	efree(pmatch);
381 	zval_ptr_dtor(&pidents);
382 
383 	if (query && IS_ARRAY == Z_TYPE_P(query)) {
384 		HashTable *ht = Z_ARRVAL_P(query);
385 
386 		smart_str_appendc(&query_str, '?');
387 		ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, zv) {
388 			if (key) {
389 				val = zval_get_string(zv);
390 				smart_str_appendl(&query_str, ZSTR_VAL(key), ZSTR_LEN(key));
391 				smart_str_appendc(&query_str, '=');
392 				smart_str_appendl(&query_str, ZSTR_VAL(val), ZSTR_LEN(val));
393 				smart_str_appendc(&query_str, '&');
394 				zend_string_release(val);
395 			}
396 		} ZEND_HASH_FOREACH_END();
397 	}
398 
399 	if (query_str.s) {
400 		size_t orig_len = ZSTR_LEN(uri);
401 		ZSTR_LEN(query_str.s)--; /* get rid of the tail & */
402 		smart_str_0(&query_str);
403 		uri = zend_string_realloc(uri, ZSTR_LEN(uri) + ZSTR_LEN(query_str.s), 0);
404 		memcpy(ZSTR_VAL(uri) + orig_len, ZSTR_VAL(query_str.s), ZSTR_LEN(query_str.s));
405 		ZSTR_VAL(uri)[ZSTR_LEN(uri)] = '\0';
406 		smart_str_free(&query_str);
407 	}
408 
409 	return uri;
410 }
411 /* }}} */
412 
413 /** {{{ proto public Yaf_Route_Rewrite::route(Yaf_Request_Abstract $request)
414  */
PHP_METHOD(yaf_route_rewrite,route)415 PHP_METHOD(yaf_route_rewrite, route) {
416 	yaf_request_t *request;
417 
418 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &request, yaf_request_ce) == FAILURE) {
419 		return;
420 	}
421 
422 	RETURN_BOOL(yaf_route_rewrite_route(getThis(), request));
423 }
424 /** }}} */
425 
426 /** {{{ proto public Yaf_Route_Rewrite::match(string $uri)
427  */
PHP_METHOD(yaf_route_rewrite,match)428 PHP_METHOD(yaf_route_rewrite, match) {
429 	zend_string *uri;
430 
431 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &uri) == FAILURE) {
432 		return;
433 	}
434 
435 	if (ZSTR_LEN(uri) == 0) {
436 		RETURN_FALSE;
437 	}
438 
439 	if (!yaf_route_rewrite_match(Z_YAFROUTEREWRITEOBJ_P(getThis()), ZSTR_VAL(uri), ZSTR_LEN(uri), return_value)) {
440 		RETURN_FALSE;
441 	}
442 }
443 /** }}} */
444 
445 /** {{{ proto public Yaf_Route_Rewrite::__construct(string $match, array $route, array $verify, string $reverse = NULL)
446  */
PHP_METHOD(yaf_route_rewrite,__construct)447 PHP_METHOD(yaf_route_rewrite, __construct) {
448 	zend_string *match;
449 	zval *route, *verify = NULL;
450 
451 	if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "Sa|a", &match, &route, &verify) ==  FAILURE) {
452 		return;
453 	}
454 
455 	yaf_route_rewrite_init(Z_YAFROUTEREWRITEOBJ_P(getThis()), match, route, verify);
456 }
457 /** }}} */
458 
459 /** {{{ proto public Yaf_Route_rewrite::assemble(array $info[, array $query = NULL])
460 */
PHP_METHOD(yaf_route_rewrite,assemble)461 PHP_METHOD(yaf_route_rewrite, assemble) {
462 	zend_string *str;
463 	zval *info, *query = NULL;
464 
465 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "a|a", &info, &query) == FAILURE) {
466 		return;
467 	}
468 
469 	if ((str = yaf_route_rewrite_assemble(Z_YAFROUTEREWRITEOBJ_P(getThis()), info, query))) {
470 		RETURN_STR(str);
471 	}
472 }
473 /* }}} */
474 
475 /** {{{ yaf_route_rewrite_methods
476  */
477 zend_function_entry yaf_route_rewrite_methods[] = {
478 	PHP_ME(yaf_route_rewrite, __construct, yaf_route_rewrite_construct_arginfo, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
479 	PHP_ME(yaf_route_rewrite, match, yaf_route_rewrite_match_arginfo, ZEND_ACC_PUBLIC)
480 	PHP_ME(yaf_route_rewrite, route, yaf_route_route_arginfo, ZEND_ACC_PUBLIC)
481 	PHP_ME(yaf_route_rewrite, assemble, yaf_route_assemble_arginfo, ZEND_ACC_PUBLIC)
482     {NULL, NULL, NULL}
483 };
484 /* }}} */
485 
486 /** {{{ YAF_STARTUP_FUNCTION
487  */
YAF_STARTUP_FUNCTION(route_rewrite)488 YAF_STARTUP_FUNCTION(route_rewrite) {
489 	zend_class_entry ce;
490 	YAF_INIT_CLASS_ENTRY(ce, "Yaf_Route_Rewrite", "Yaf\\Route\\Rewrite", yaf_route_rewrite_methods);
491 	yaf_route_rewrite_ce = zend_register_internal_class_ex(&ce, NULL);
492 	yaf_route_rewrite_ce->ce_flags |= ZEND_ACC_FINAL;
493 	yaf_route_rewrite_ce->create_object = yaf_route_rewrite_new;
494 	yaf_route_rewrite_ce->serialize = zend_class_serialize_deny;
495 	yaf_route_rewrite_ce->unserialize = zend_class_unserialize_deny;
496 
497 	zend_class_implements(yaf_route_rewrite_ce, 1, yaf_route_ce);
498 
499 	memcpy(&yaf_route_rewrite_obj_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
500 	yaf_route_rewrite_obj_handlers.free_obj = yaf_route_rewrite_object_free;
501 	yaf_route_rewrite_obj_handlers.clone_obj = NULL;
502 	yaf_route_rewrite_obj_handlers.get_gc = yaf_fake_get_gc;
503 	yaf_route_rewrite_obj_handlers.get_properties = yaf_route_rewrite_get_properties;
504 
505 	return SUCCESS;
506 }
507 /* }}} */
508 
509 /*
510  * Local variables:
511  * tab-width: 4
512  * c-basic-offset: 4
513  * End:
514  * vim600: noet sw=4 ts=4 fdm=marker
515  * vim<600: noet sw=4 ts=4
516  */
517 
518