1 /*
2  * Copyright 2014-2017 MongoDB, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <php.h>
18 #include <Zend/zend_interfaces.h>
19 #include <ext/standard/php_var.h>
20 #include <zend_smart_str.h>
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "phongo_compat.h"
27 #include "php_phongo.h"
28 
29 zend_class_entry* php_phongo_regex_ce;
30 
31 /* qsort() compare callback for alphabetizing regex flags upon initialization */
php_phongo_regex_compare_flags(const void * f1,const void * f2)32 static int php_phongo_regex_compare_flags(const void* f1, const void* f2) /* {{{ */
33 {
34 	if (*(const char*) f1 == *(const char*) f2) {
35 		return 0;
36 	}
37 
38 	return (*(const char*) f1 > *(const char*) f2) ? 1 : -1;
39 } /* }}} */
40 
41 /* Initialize the object and return whether it was successful. An exception will
42  * be thrown on error. */
php_phongo_regex_init(php_phongo_regex_t * intern,const char * pattern,size_t pattern_len,const char * flags,size_t flags_len)43 static bool php_phongo_regex_init(php_phongo_regex_t* intern, const char* pattern, size_t pattern_len, const char* flags, size_t flags_len) /* {{{ */
44 {
45 	if (strlen(pattern) != (size_t) pattern_len) {
46 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Pattern cannot contain null bytes");
47 		return false;
48 	}
49 	intern->pattern     = estrndup(pattern, pattern_len);
50 	intern->pattern_len = pattern_len;
51 
52 	if (flags) {
53 		if (strlen(flags) != (size_t) flags_len) {
54 			phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Flags cannot contain null bytes");
55 			return false;
56 		}
57 		intern->flags     = estrndup(flags, flags_len);
58 		intern->flags_len = flags_len;
59 		/* Ensure flags are alphabetized upon initialization */
60 		qsort((void*) intern->flags, flags_len, 1, php_phongo_regex_compare_flags);
61 	} else {
62 		intern->flags     = estrdup("");
63 		intern->flags_len = 0;
64 	}
65 
66 	return true;
67 } /* }}} */
68 
69 /* Initialize the object from a HashTable and return whether it was successful.
70  * An exception will be thrown on error. */
php_phongo_regex_init_from_hash(php_phongo_regex_t * intern,HashTable * props)71 static bool php_phongo_regex_init_from_hash(php_phongo_regex_t* intern, HashTable* props) /* {{{ */
72 {
73 	zval *pattern, *flags;
74 
75 	if ((pattern = zend_hash_str_find(props, "pattern", sizeof("pattern") - 1)) && Z_TYPE_P(pattern) == IS_STRING &&
76 		(flags = zend_hash_str_find(props, "flags", sizeof("flags") - 1)) && Z_TYPE_P(flags) == IS_STRING) {
77 
78 		return php_phongo_regex_init(intern, Z_STRVAL_P(pattern), Z_STRLEN_P(pattern), Z_STRVAL_P(flags), Z_STRLEN_P(flags));
79 	}
80 
81 	phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "%s initialization requires \"pattern\" and \"flags\" string fields", ZSTR_VAL(php_phongo_regex_ce->name));
82 	return false;
83 } /* }}} */
84 
85 /* {{{ proto void MongoDB\BSON\Regex::__construct(string $pattern [, string $flags])
86    Constructs a new BSON regular expression type. */
PHP_METHOD(Regex,__construct)87 static PHP_METHOD(Regex, __construct)
88 {
89 	zend_error_handling error_handling;
90 	php_phongo_regex_t* intern;
91 	char*               pattern;
92 	size_t              pattern_len;
93 	char*               flags     = NULL;
94 	size_t              flags_len = 0;
95 
96 	intern = Z_REGEX_OBJ_P(getThis());
97 
98 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
99 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &pattern, &pattern_len, &flags, &flags_len) == FAILURE) {
100 		zend_restore_error_handling(&error_handling);
101 		return;
102 	}
103 	zend_restore_error_handling(&error_handling);
104 
105 	php_phongo_regex_init(intern, pattern, pattern_len, flags, flags_len);
106 } /* }}} */
107 
108 /* {{{ proto string MongoDB\BSON\Regex::getPattern()
109 */
PHP_METHOD(Regex,getPattern)110 static PHP_METHOD(Regex, getPattern)
111 {
112 	zend_error_handling error_handling;
113 	php_phongo_regex_t* intern;
114 
115 	intern = Z_REGEX_OBJ_P(getThis());
116 
117 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
118 	if (zend_parse_parameters_none() == FAILURE) {
119 		zend_restore_error_handling(&error_handling);
120 		return;
121 	}
122 	zend_restore_error_handling(&error_handling);
123 
124 	RETURN_STRINGL(intern->pattern, intern->pattern_len);
125 } /* }}} */
126 
127 /* {{{ proto string MongoDB\BSON\Regex::getFlags()
128 */
PHP_METHOD(Regex,getFlags)129 static PHP_METHOD(Regex, getFlags)
130 {
131 	zend_error_handling error_handling;
132 	php_phongo_regex_t* intern;
133 
134 	intern = Z_REGEX_OBJ_P(getThis());
135 
136 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
137 	if (zend_parse_parameters_none() == FAILURE) {
138 		zend_restore_error_handling(&error_handling);
139 		return;
140 	}
141 	zend_restore_error_handling(&error_handling);
142 
143 	RETURN_STRINGL(intern->flags, intern->flags_len);
144 } /* }}} */
145 
146 /* {{{ proto void MongoDB\BSON\Regex::__set_state(array $properties)
147 */
PHP_METHOD(Regex,__set_state)148 static PHP_METHOD(Regex, __set_state)
149 {
150 	zend_error_handling error_handling;
151 	php_phongo_regex_t* intern;
152 	HashTable*          props;
153 	zval*               array;
154 
155 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
156 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &array) == FAILURE) {
157 		zend_restore_error_handling(&error_handling);
158 		return;
159 	}
160 	zend_restore_error_handling(&error_handling);
161 
162 	object_init_ex(return_value, php_phongo_regex_ce);
163 
164 	intern = Z_REGEX_OBJ_P(return_value);
165 	props  = Z_ARRVAL_P(array);
166 
167 	php_phongo_regex_init_from_hash(intern, props);
168 } /* }}} */
169 
170 /* {{{ proto string MongoDB\BSON\Regex::__toString()
171    Returns a string in the form: /pattern/flags */
PHP_METHOD(Regex,__toString)172 static PHP_METHOD(Regex, __toString)
173 {
174 	zend_error_handling error_handling;
175 	php_phongo_regex_t* intern;
176 	char*               regex;
177 	int                 regex_len;
178 
179 	intern = Z_REGEX_OBJ_P(getThis());
180 
181 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
182 	if (zend_parse_parameters_none() == FAILURE) {
183 		zend_restore_error_handling(&error_handling);
184 		return;
185 	}
186 	zend_restore_error_handling(&error_handling);
187 
188 	regex_len = spprintf(&regex, 0, "/%s/%s", intern->pattern, intern->flags);
189 	RETVAL_STRINGL(regex, regex_len);
190 	efree(regex);
191 } /* }}} */
192 
193 /* {{{ proto array MongoDB\BSON\Regex::jsonSerialize()
194 */
PHP_METHOD(Regex,jsonSerialize)195 static PHP_METHOD(Regex, jsonSerialize)
196 {
197 	zend_error_handling error_handling;
198 	php_phongo_regex_t* intern;
199 
200 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
201 	if (zend_parse_parameters_none() == FAILURE) {
202 		zend_restore_error_handling(&error_handling);
203 		return;
204 	}
205 	zend_restore_error_handling(&error_handling);
206 
207 	intern = Z_REGEX_OBJ_P(getThis());
208 
209 	array_init_size(return_value, 2);
210 	ADD_ASSOC_STRINGL(return_value, "$regex", intern->pattern, intern->pattern_len);
211 	ADD_ASSOC_STRINGL(return_value, "$options", intern->flags, intern->flags_len);
212 } /* }}} */
213 
214 /* {{{ proto string MongoDB\BSON\Regex::serialize()
215 */
PHP_METHOD(Regex,serialize)216 static PHP_METHOD(Regex, serialize)
217 {
218 	zend_error_handling  error_handling;
219 	php_phongo_regex_t*  intern;
220 	zval                 retval;
221 	php_serialize_data_t var_hash;
222 	smart_str            buf = { 0 };
223 
224 	intern = Z_REGEX_OBJ_P(getThis());
225 
226 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
227 	if (zend_parse_parameters_none() == FAILURE) {
228 		zend_restore_error_handling(&error_handling);
229 		return;
230 	}
231 	zend_restore_error_handling(&error_handling);
232 
233 	array_init_size(&retval, 2);
234 	ADD_ASSOC_STRINGL(&retval, "pattern", intern->pattern, intern->pattern_len);
235 	ADD_ASSOC_STRINGL(&retval, "flags", intern->flags, intern->flags_len);
236 
237 	PHP_VAR_SERIALIZE_INIT(var_hash);
238 	php_var_serialize(&buf, &retval, &var_hash);
239 	smart_str_0(&buf);
240 	PHP_VAR_SERIALIZE_DESTROY(var_hash);
241 
242 	PHONGO_RETVAL_SMART_STR(buf);
243 
244 	smart_str_free(&buf);
245 	zval_ptr_dtor(&retval);
246 } /* }}} */
247 
248 /* {{{ proto void MongoDB\BSON\Regex::unserialize(string $serialized)
249 */
PHP_METHOD(Regex,unserialize)250 static PHP_METHOD(Regex, unserialize)
251 {
252 	zend_error_handling    error_handling;
253 	php_phongo_regex_t*    intern;
254 	char*                  serialized;
255 	size_t                 serialized_len;
256 	zval                   props;
257 	php_unserialize_data_t var_hash;
258 
259 	intern = Z_REGEX_OBJ_P(getThis());
260 
261 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
262 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &serialized, &serialized_len) == FAILURE) {
263 		zend_restore_error_handling(&error_handling);
264 		return;
265 	}
266 	zend_restore_error_handling(&error_handling);
267 
268 	PHP_VAR_UNSERIALIZE_INIT(var_hash);
269 	if (!php_var_unserialize(&props, (const unsigned char**) &serialized, (unsigned char*) serialized + serialized_len, &var_hash)) {
270 		zval_ptr_dtor(&props);
271 		phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE, "%s unserialization failed", ZSTR_VAL(php_phongo_regex_ce->name));
272 
273 		PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
274 		return;
275 	}
276 	PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
277 
278 	php_phongo_regex_init_from_hash(intern, HASH_OF(&props));
279 	zval_ptr_dtor(&props);
280 } /* }}} */
281 
282 /* {{{ MongoDB\BSON\Regex function entries */
283 ZEND_BEGIN_ARG_INFO_EX(ai_Regex___construct, 0, 0, 1)
284 	ZEND_ARG_INFO(0, pattern)
285 	ZEND_ARG_INFO(0, flags)
286 ZEND_END_ARG_INFO()
287 
288 ZEND_BEGIN_ARG_INFO_EX(ai_Regex___set_state, 0, 0, 1)
289 	ZEND_ARG_ARRAY_INFO(0, properties, 0)
290 ZEND_END_ARG_INFO()
291 
292 ZEND_BEGIN_ARG_INFO_EX(ai_Regex_unserialize, 0, 0, 1)
293 	ZEND_ARG_INFO(0, serialized)
294 ZEND_END_ARG_INFO()
295 
296 ZEND_BEGIN_ARG_INFO_EX(ai_Regex_void, 0, 0, 0)
297 ZEND_END_ARG_INFO()
298 
299 static zend_function_entry php_phongo_regex_me[] = {
300 	/* clang-format off */
301 	PHP_ME(Regex, __construct, ai_Regex___construct, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
302 	PHP_ME(Regex, __set_state, ai_Regex___set_state, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC)
303 	PHP_ME(Regex, __toString, ai_Regex_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
304 	PHP_ME(Regex, jsonSerialize, ai_Regex_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
305 	PHP_ME(Regex, serialize, ai_Regex_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
306 	PHP_ME(Regex, unserialize, ai_Regex_unserialize, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
307 	PHP_ME(Regex, getPattern, ai_Regex_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
308 	PHP_ME(Regex, getFlags, ai_Regex_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
309 	PHP_FE_END
310 	/* clang-format on */
311 };
312 /* }}} */
313 
314 /* {{{ MongoDB\BSON\Regex object handlers */
315 static zend_object_handlers php_phongo_handler_regex;
316 
php_phongo_regex_free_object(zend_object * object)317 static void php_phongo_regex_free_object(zend_object* object) /* {{{ */
318 {
319 	php_phongo_regex_t* intern = Z_OBJ_REGEX(object);
320 
321 	zend_object_std_dtor(&intern->std);
322 
323 	if (intern->pattern) {
324 		efree(intern->pattern);
325 	}
326 
327 	if (intern->flags) {
328 		efree(intern->flags);
329 	}
330 
331 	if (intern->properties) {
332 		zend_hash_destroy(intern->properties);
333 		FREE_HASHTABLE(intern->properties);
334 	}
335 } /* }}} */
336 
php_phongo_regex_create_object(zend_class_entry * class_type)337 static zend_object* php_phongo_regex_create_object(zend_class_entry* class_type) /* {{{ */
338 {
339 	php_phongo_regex_t* intern = NULL;
340 
341 	intern = PHONGO_ALLOC_OBJECT_T(php_phongo_regex_t, class_type);
342 
343 	zend_object_std_init(&intern->std, class_type);
344 	object_properties_init(&intern->std, class_type);
345 
346 	intern->std.handlers = &php_phongo_handler_regex;
347 
348 	return &intern->std;
349 } /* }}} */
350 
php_phongo_regex_clone_object(phongo_compat_object_handler_type * object)351 static zend_object* php_phongo_regex_clone_object(phongo_compat_object_handler_type* object) /* {{{ */
352 {
353 	php_phongo_regex_t* intern;
354 	php_phongo_regex_t* new_intern;
355 	zend_object*        new_object;
356 
357 	intern     = Z_OBJ_REGEX(PHONGO_COMPAT_GET_OBJ(object));
358 	new_object = php_phongo_regex_create_object(PHONGO_COMPAT_GET_OBJ(object)->ce);
359 
360 	new_intern = Z_OBJ_REGEX(new_object);
361 	zend_objects_clone_members(&new_intern->std, &intern->std);
362 
363 	php_phongo_regex_init(new_intern, intern->pattern, intern->pattern_len, intern->flags, intern->flags_len);
364 
365 	return new_object;
366 } /* }}} */
367 
php_phongo_regex_compare_objects(zval * o1,zval * o2)368 static int php_phongo_regex_compare_objects(zval* o1, zval* o2) /* {{{ */
369 {
370 	php_phongo_regex_t *intern1, *intern2;
371 	int                 retval;
372 
373 	ZEND_COMPARE_OBJECTS_FALLBACK(o1, o2);
374 
375 	intern1 = Z_REGEX_OBJ_P(o1);
376 	intern2 = Z_REGEX_OBJ_P(o2);
377 
378 	/* MongoDB compares the pattern string before the flags. */
379 	retval = strcmp(intern1->pattern, intern2->pattern);
380 
381 	if (retval != 0) {
382 		return retval;
383 	}
384 
385 	return strcmp(intern1->flags, intern2->flags);
386 } /* }}} */
387 
php_phongo_regex_get_properties_hash(phongo_compat_object_handler_type * object,bool is_debug)388 static HashTable* php_phongo_regex_get_properties_hash(phongo_compat_object_handler_type* object, bool is_debug) /* {{{ */
389 {
390 	php_phongo_regex_t* intern;
391 	HashTable*          props;
392 
393 	intern = Z_OBJ_REGEX(PHONGO_COMPAT_GET_OBJ(object));
394 
395 	PHONGO_GET_PROPERTY_HASH_INIT_PROPS(is_debug, intern, props, 2);
396 
397 	if (!intern->pattern) {
398 		return props;
399 	}
400 
401 	{
402 		zval pattern, flags;
403 
404 		ZVAL_STRINGL(&pattern, intern->pattern, intern->pattern_len);
405 		zend_hash_str_update(props, "pattern", sizeof("pattern") - 1, &pattern);
406 
407 		ZVAL_STRINGL(&flags, intern->flags, intern->flags_len);
408 		zend_hash_str_update(props, "flags", sizeof("flags") - 1, &flags);
409 	}
410 
411 	return props;
412 } /* }}} */
413 
php_phongo_regex_get_debug_info(phongo_compat_object_handler_type * object,int * is_temp)414 static HashTable* php_phongo_regex_get_debug_info(phongo_compat_object_handler_type* object, int* is_temp) /* {{{ */
415 {
416 	*is_temp = 1;
417 	return php_phongo_regex_get_properties_hash(object, true);
418 } /* }}} */
419 
php_phongo_regex_get_properties(phongo_compat_object_handler_type * object)420 static HashTable* php_phongo_regex_get_properties(phongo_compat_object_handler_type* object) /* {{{ */
421 {
422 	return php_phongo_regex_get_properties_hash(object, false);
423 } /* }}} */
424 /* }}} */
425 
php_phongo_regex_init_ce(INIT_FUNC_ARGS)426 void php_phongo_regex_init_ce(INIT_FUNC_ARGS) /* {{{ */
427 {
428 	zend_class_entry ce;
429 
430 	INIT_NS_CLASS_ENTRY(ce, "MongoDB\\BSON", "Regex", php_phongo_regex_me);
431 	php_phongo_regex_ce                = zend_register_internal_class(&ce);
432 	php_phongo_regex_ce->create_object = php_phongo_regex_create_object;
433 	PHONGO_CE_FINAL(php_phongo_regex_ce);
434 
435 	zend_class_implements(php_phongo_regex_ce, 1, php_phongo_regex_interface_ce);
436 	zend_class_implements(php_phongo_regex_ce, 1, php_phongo_type_ce);
437 	zend_class_implements(php_phongo_regex_ce, 1, zend_ce_serializable);
438 	zend_class_implements(php_phongo_regex_ce, 1, php_phongo_json_serializable_ce);
439 
440 	memcpy(&php_phongo_handler_regex, phongo_get_std_object_handlers(), sizeof(zend_object_handlers));
441 	PHONGO_COMPAT_SET_COMPARE_OBJECTS_HANDLER(regex);
442 	php_phongo_handler_regex.clone_obj      = php_phongo_regex_clone_object;
443 	php_phongo_handler_regex.get_debug_info = php_phongo_regex_get_debug_info;
444 	php_phongo_handler_regex.get_properties = php_phongo_regex_get_properties;
445 	php_phongo_handler_regex.free_obj       = php_phongo_regex_free_object;
446 	php_phongo_handler_regex.offset         = XtOffsetOf(php_phongo_regex_t, std);
447 } /* }}} */
448 
449 /*
450  * Local variables:
451  * tab-width: 4
452  * c-basic-offset: 4
453  * End:
454  * vim600: noet sw=4 ts=4 fdm=marker
455  * vim<600: noet sw=4 ts=4
456  */
457