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