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(®ex->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 ®ex->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(®ex->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