1 /*
2    +----------------------------------------------------------------------+
3    | This source file is subject to version 3.01 of the PHP license,      |
4    | that is bundled with this package in the file LICENSE, and is        |
5    | available through the world-wide-web at the following url:           |
6    | https://www.php.net/license/3_01.txt                                 |
7    | If you did not receive a copy of the PHP license and are unable to   |
8    | obtain it through the world-wide-web, please send a note to          |
9    | license@php.net so we can mail you a copy immediately.               |
10    +----------------------------------------------------------------------+
11    | Authors: Hans-Peter Oeri (University of St.Gallen) <hp@oeri.ch>      |
12    +----------------------------------------------------------------------+
13  */
14 
15 #include <stdlib.h>
16 #include <unicode/ures.h>
17 #include <unicode/uenum.h>
18 
19 #include <zend.h>
20 #include <Zend/zend_exceptions.h>
21 #include <Zend/zend_interfaces.h>
22 #include <php.h>
23 
24 #include "php_intl.h"
25 #include "intl_data.h"
26 #include "intl_common.h"
27 
28 #include "resourcebundle/resourcebundle.h"
29 #include "resourcebundle/resourcebundle_iterator.h"
30 #include "resourcebundle/resourcebundle_class.h"
31 #include "resourcebundle/resourcebundle_arginfo.h"
32 
33 zend_class_entry *ResourceBundle_ce_ptr = NULL;
34 
35 static zend_object_handlers ResourceBundle_object_handlers;
36 
37 /* {{{ ResourceBundle_object_free */
ResourceBundle_object_free(zend_object * object)38 static void ResourceBundle_object_free( zend_object *object )
39 {
40 	ResourceBundle_object *rb = php_intl_resourcebundle_fetch_object(object);
41 
42 	// only free local errors
43 	intl_error_reset( INTL_DATA_ERROR_P(rb) );
44 
45 	if (rb->me) {
46 		ures_close( rb->me );
47 	}
48 	if (rb->child) {
49 		ures_close( rb->child );
50 	}
51 
52 	zend_object_std_dtor( &rb->zend );
53 }
54 /* }}} */
55 
56 /* {{{ ResourceBundle_object_create */
ResourceBundle_object_create(zend_class_entry * ce)57 static zend_object *ResourceBundle_object_create( zend_class_entry *ce )
58 {
59 	ResourceBundle_object *rb;
60 
61 	rb = zend_object_alloc(sizeof(ResourceBundle_object), ce);
62 
63 	zend_object_std_init( &rb->zend, ce );
64 	object_properties_init( &rb->zend, ce);
65 
66 	intl_error_init( INTL_DATA_ERROR_P(rb) );
67 	rb->me = NULL;
68 	rb->child = NULL;
69 
70 	return &rb->zend;
71 }
72 /* }}} */
73 
74 /* {{{ ResourceBundle_ctor */
resourcebundle_ctor(INTERNAL_FUNCTION_PARAMETERS,zend_error_handling * error_handling,bool * error_handling_replaced)75 static int resourcebundle_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handling *error_handling, bool *error_handling_replaced)
76 {
77 	char           *bundlename;
78 	size_t		bundlename_len = 0;
79 	char           *locale;
80 	size_t		locale_len = 0;
81 	bool	fallback = true;
82 
83 	zval                  *object = return_value;
84 	ResourceBundle_object *rb = Z_INTL_RESOURCEBUNDLE_P( object );
85 
86 	intl_error_reset( NULL );
87 
88 	ZEND_PARSE_PARAMETERS_START(2, 3)
89 		Z_PARAM_STRING_OR_NULL(locale, locale_len)
90 		Z_PARAM_STRING_OR_NULL(bundlename, bundlename_len)
91 		Z_PARAM_OPTIONAL
92 		Z_PARAM_BOOL(fallback)
93 	ZEND_PARSE_PARAMETERS_END_EX(return FAILURE);
94 
95 	if (error_handling != NULL) {
96 		zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, error_handling);
97 		*error_handling_replaced = 1;
98 	}
99 
100 	if (rb->me) {
101 		zend_throw_error(NULL, "ResourceBundle object is already constructed");
102 		return FAILURE;
103 	}
104 
105 	INTL_CHECK_LOCALE_LEN_OR_FAILURE(locale_len);
106 
107 	if (locale == NULL) {
108 		locale = (char *)intl_locale_get_default();
109 	}
110 
111 	if (bundlename_len >= MAXPATHLEN) {
112 		zend_argument_value_error(2, "is too long");
113 		return FAILURE;
114 	}
115 
116 	if (fallback) {
117 		rb->me = ures_open(bundlename, locale, &INTL_DATA_ERROR_CODE(rb));
118 	} else {
119 		rb->me = ures_openDirect(bundlename, locale, &INTL_DATA_ERROR_CODE(rb));
120 	}
121 
122 	INTL_CTOR_CHECK_STATUS(rb, "resourcebundle_ctor: Cannot load libICU resource bundle");
123 
124 	if (!fallback && (INTL_DATA_ERROR_CODE(rb) == U_USING_FALLBACK_WARNING ||
125 			INTL_DATA_ERROR_CODE(rb) == U_USING_DEFAULT_WARNING)) {
126 		char *pbuf;
127 		intl_errors_set_code(NULL, INTL_DATA_ERROR_CODE(rb));
128 		spprintf(&pbuf, 0, "resourcebundle_ctor: Cannot load libICU resource "
129 				"'%s' without fallback from %s to %s",
130 				bundlename ? bundlename : "(default data)", locale,
131 				ures_getLocaleByType(
132 					rb->me, ULOC_ACTUAL_LOCALE, &INTL_DATA_ERROR_CODE(rb)));
133 		intl_errors_set_custom_msg(INTL_DATA_ERROR_P(rb), pbuf, 1);
134 		efree(pbuf);
135 		return FAILURE;
136 	}
137 
138 	return SUCCESS;
139 }
140 /* }}} */
141 
142 /* {{{ ResourceBundle object constructor */
PHP_METHOD(ResourceBundle,__construct)143 PHP_METHOD( ResourceBundle, __construct )
144 {
145 	zend_error_handling error_handling;
146 	bool error_handling_replaced = 0;
147 
148 	return_value = ZEND_THIS;
149 	if (resourcebundle_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, &error_handling, &error_handling_replaced) == FAILURE) {
150 		if (!EG(exception)) {
151 			zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
152 		}
153 	}
154 	if (error_handling_replaced) {
155 		zend_restore_error_handling(&error_handling);
156 	}
157 }
158 /* }}} */
159 
160 /* {{{ */
PHP_FUNCTION(resourcebundle_create)161 PHP_FUNCTION( resourcebundle_create )
162 {
163 	object_init_ex( return_value, ResourceBundle_ce_ptr );
164 	if (resourcebundle_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, NULL, NULL) == FAILURE) {
165 		zval_ptr_dtor(return_value);
166 		RETURN_NULL();
167 	}
168 }
169 /* }}} */
170 
171 /* {{{ resourcebundle_array_fetch */
resource_bundle_array_fetch(zend_object * object,zend_string * offset_str,zend_long offset_int,zval * return_value,bool fallback,uint32_t offset_arg_num)172 static zval *resource_bundle_array_fetch(
173 	zend_object *object, zend_string *offset_str, zend_long offset_int,
174 	zval *return_value, bool fallback, uint32_t offset_arg_num)
175 {
176 	int32_t index = 0;
177 	char *key = NULL;
178 	bool is_numeric = offset_str == NULL;
179 	char *pbuf;
180 	ResourceBundle_object *rb;
181 
182 	rb = php_intl_resourcebundle_fetch_object(object);
183 	intl_error_reset(NULL);
184 	intl_error_reset(INTL_DATA_ERROR_P(rb));
185 
186 	if (offset_str) {
187 		if (UNEXPECTED(ZSTR_LEN(offset_str) == 0)) {
188 			if (offset_arg_num) {
189 				zend_argument_must_not_be_empty_error(offset_arg_num);
190 			} else {
191 				zend_value_error("Offset must not be empty");
192 			}
193 			return NULL;
194 		}
195 		key = ZSTR_VAL(offset_str);
196 		rb->child = ures_getByKey(rb->me, key, rb->child, &INTL_DATA_ERROR_CODE(rb) );
197 	} else {
198 		if (UNEXPECTED(offset_int < (zend_long)INT32_MIN || offset_int > (zend_long)INT32_MAX)) {
199 			if (offset_arg_num) {
200 				zend_argument_value_error(offset_arg_num, "index must be between %d and %d", INT32_MIN, INT32_MAX);
201 			} else {
202 				zend_value_error("Index must be between %d and %d", INT32_MIN, INT32_MAX);
203 			}
204 			return NULL;
205 		}
206 		index = (int32_t)offset_int;
207 		rb->child = ures_getByIndex(rb->me, index, rb->child, &INTL_DATA_ERROR_CODE(rb));
208 	}
209 
210 	intl_error_set_code( NULL, INTL_DATA_ERROR_CODE(rb) );
211 	if (U_FAILURE(INTL_DATA_ERROR_CODE(rb))) {
212 		if (is_numeric) {
213 			spprintf( &pbuf, 0, "Cannot load resource element %d", index );
214 		} else {
215 			spprintf( &pbuf, 0, "Cannot load resource element '%s'", key );
216 		}
217 		intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1 );
218 		efree(pbuf);
219 		RETVAL_NULL();
220 		return return_value;
221 	}
222 
223 	if (!fallback && (INTL_DATA_ERROR_CODE(rb) == U_USING_FALLBACK_WARNING || INTL_DATA_ERROR_CODE(rb) == U_USING_DEFAULT_WARNING)) {
224 		UErrorCode icuerror;
225 		const char * locale = ures_getLocaleByType( rb->me, ULOC_ACTUAL_LOCALE, &icuerror );
226 		if (is_numeric) {
227 			spprintf(&pbuf, 0, "Cannot load element %d without fallback from to %s", index, locale);
228 		} else {
229 			spprintf(&pbuf, 0, "Cannot load element '%s' without fallback from to %s", key, locale);
230 		}
231 		intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1);
232 		efree(pbuf);
233 		RETVAL_NULL();
234 		return return_value;
235 	}
236 
237 	resourcebundle_extract_value( return_value, rb );
238 	return return_value;
239 }
240 /* }}} */
241 
242 /* {{{ resourcebundle_array_get */
resourcebundle_array_get(zend_object * object,zval * offset,int type,zval * rv)243 zval *resourcebundle_array_get(zend_object *object, zval *offset, int type, zval *rv)
244 {
245 	if (offset == NULL) {
246 		zend_throw_error(NULL, "Cannot apply [] to ResourceBundle object");
247 		return NULL;
248 	}
249 
250 	ZVAL_DEREF(offset);
251 	if (Z_TYPE_P(offset) == IS_LONG) {
252 		return resource_bundle_array_fetch(object, /* offset_str */ NULL, Z_LVAL_P(offset), rv, /* fallback */ true, /* arg_num */ 0);
253 	} else if (Z_TYPE_P(offset) == IS_STRING) {
254 		return resource_bundle_array_fetch(object, Z_STR_P(offset), /* offset_int */ 0, rv, /* fallback */ true, /* arg_num */ 0);
255 	} else {
256 		zend_illegal_container_offset(object->ce->name, offset, type);
257 		return NULL;
258 	}
259 }
260 /* }}} */
261 
262 /* {{{ Get resource identified by numerical index or key name. */
PHP_FUNCTION(resourcebundle_get)263 PHP_FUNCTION( resourcebundle_get )
264 {
265 	bool fallback = true;
266 	zend_object *resource_bundle = NULL;
267 	zend_string *offset_str = NULL;
268 	zend_long offset_long = 0;
269 
270 	ZEND_PARSE_PARAMETERS_START(2, 3)
271 		Z_PARAM_OBJ_OF_CLASS(resource_bundle, ResourceBundle_ce_ptr)
272 		Z_PARAM_STR_OR_LONG(offset_str, offset_long)
273 		Z_PARAM_OPTIONAL
274 		Z_PARAM_BOOL(fallback)
275 	ZEND_PARSE_PARAMETERS_END();
276 
277 	zval *retval = resource_bundle_array_fetch(resource_bundle, offset_str, offset_long, return_value, fallback, /* arg_num */ 2);
278 	if (!retval) {
279 		RETURN_THROWS();
280 	}
281 }
282 /* }}} */
283 
PHP_METHOD(ResourceBundle,get)284 PHP_METHOD(ResourceBundle , get)
285 {
286 	bool fallback = true;
287 	zend_string *offset_str = NULL;
288 	zend_long offset_long = 0;
289 
290 	ZEND_PARSE_PARAMETERS_START(1, 2)
291 		Z_PARAM_STR_OR_LONG(offset_str, offset_long)
292 		Z_PARAM_OPTIONAL
293 		Z_PARAM_BOOL(fallback)
294 	ZEND_PARSE_PARAMETERS_END();
295 
296 	zval *retval = resource_bundle_array_fetch(Z_OBJ_P(ZEND_THIS), offset_str, offset_long, return_value, fallback, /* arg_num */ 1);
297 	if (!retval) {
298 		RETURN_THROWS();
299 	}
300 }
301 /* }}} */
302 
303 /* {{{ resourcebundle_array_count */
resourcebundle_array_count(zend_object * object,zend_long * count)304 static zend_result resourcebundle_array_count(zend_object *object, zend_long *count)
305 {
306 	ResourceBundle_object *rb = php_intl_resourcebundle_fetch_object(object);
307 
308 	if (rb->me == NULL) {
309 		intl_errors_set(&rb->error, U_ILLEGAL_ARGUMENT_ERROR,
310 				"Found unconstructed ResourceBundle", 0);
311 		return 0;
312 	}
313 
314 	*count = ures_getSize( rb->me );
315 
316 	return SUCCESS;
317 }
318 /* }}} */
319 
320 /* {{{ Get resources count */
PHP_FUNCTION(resourcebundle_count)321 PHP_FUNCTION( resourcebundle_count )
322 {
323 	int32_t                len;
324 	RESOURCEBUNDLE_METHOD_INIT_VARS;
325 
326 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "O", &object, ResourceBundle_ce_ptr ) == FAILURE ) {
327 		RETURN_THROWS();
328 	}
329 
330 	RESOURCEBUNDLE_METHOD_FETCH_OBJECT;
331 
332 	len = ures_getSize( rb->me );
333 	RETURN_LONG( len );
334 }
335 
336 /* {{{ Get available locales from ResourceBundle name */
PHP_FUNCTION(resourcebundle_locales)337 PHP_FUNCTION( resourcebundle_locales )
338 {
339 	char * bundlename;
340 	size_t    bundlename_len = 0;
341 	const char * entry;
342 	int entry_len;
343 	UEnumeration *icuenum;
344 	UErrorCode   icuerror = U_ZERO_ERROR;
345 
346 	intl_errors_reset( NULL );
347 
348 	ZEND_PARSE_PARAMETERS_START(1, 1)
349 		Z_PARAM_STRING(bundlename, bundlename_len)
350 	ZEND_PARSE_PARAMETERS_END();
351 
352 	if (bundlename_len >= MAXPATHLEN) {
353 		zend_argument_value_error(1, "is too long");
354 		RETURN_THROWS();
355 	}
356 
357 	if(bundlename_len == 0) {
358 		// fetch default locales list
359 		bundlename = NULL;
360 	}
361 
362 	icuenum = ures_openAvailableLocales( bundlename, &icuerror );
363 	INTL_CHECK_STATUS(icuerror, "Cannot fetch locales list");
364 
365 	uenum_reset( icuenum, &icuerror );
366 	INTL_CHECK_STATUS(icuerror, "Cannot iterate locales list");
367 
368 	array_init( return_value );
369 	while ((entry = uenum_next( icuenum, &entry_len, &icuerror ))) {
370 		add_next_index_stringl( return_value, (char *) entry, entry_len);
371 	}
372 	uenum_close( icuenum );
373 }
374 /* }}} */
375 
376 /* {{{ Get text description for ResourceBundle's last error code. */
PHP_FUNCTION(resourcebundle_get_error_code)377 PHP_FUNCTION( resourcebundle_get_error_code )
378 {
379 	RESOURCEBUNDLE_METHOD_INIT_VARS;
380 
381 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "O",
382 		&object, ResourceBundle_ce_ptr ) == FAILURE )
383 	{
384 		RETURN_THROWS();
385 	}
386 
387 	rb = Z_INTL_RESOURCEBUNDLE_P( object );
388 
389 	RETURN_LONG(INTL_DATA_ERROR_CODE(rb));
390 }
391 /* }}} */
392 
393 /* {{{ Get text description for ResourceBundle's last error. */
PHP_FUNCTION(resourcebundle_get_error_message)394 PHP_FUNCTION( resourcebundle_get_error_message )
395 {
396 	zend_string* message = NULL;
397 	RESOURCEBUNDLE_METHOD_INIT_VARS;
398 
399 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "O",
400 		&object, ResourceBundle_ce_ptr ) == FAILURE )
401 	{
402 		RETURN_THROWS();
403 	}
404 
405 	rb = Z_INTL_RESOURCEBUNDLE_P( object );
406 	message = intl_error_get_message(INTL_DATA_ERROR_P(rb));
407 	RETURN_STR(message);
408 }
409 /* }}} */
410 
PHP_METHOD(ResourceBundle,getIterator)411 PHP_METHOD(ResourceBundle, getIterator) {
412 	ZEND_PARSE_PARAMETERS_NONE();
413 
414 	zend_create_internal_iterator_zval(return_value, ZEND_THIS);
415 }
416 
417 /* {{{ resourcebundle_register_class
418  * Initialize 'ResourceBundle' class
419  */
resourcebundle_register_class(void)420 void resourcebundle_register_class( void )
421 {
422 	ResourceBundle_ce_ptr = register_class_ResourceBundle(zend_ce_aggregate, zend_ce_countable);
423 	ResourceBundle_ce_ptr->create_object = ResourceBundle_object_create;
424 	ResourceBundle_ce_ptr->default_object_handlers = &ResourceBundle_object_handlers;
425 	ResourceBundle_ce_ptr->get_iterator = resourcebundle_get_iterator;
426 
427 	ResourceBundle_object_handlers = std_object_handlers;
428 	ResourceBundle_object_handlers.offset = XtOffsetOf(ResourceBundle_object, zend);
429 	ResourceBundle_object_handlers.clone_obj	  = NULL; /* ICU ResourceBundle has no clone implementation */
430 	ResourceBundle_object_handlers.free_obj = ResourceBundle_object_free;
431 	ResourceBundle_object_handlers.read_dimension = resourcebundle_array_get;
432 	ResourceBundle_object_handlers.count_elements = resourcebundle_array_count;
433 }
434 /* }}} */
435