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 	rb->zend.handlers = &ResourceBundle_object_handlers;
71 
72 	return &rb->zend;
73 }
74 /* }}} */
75 
76 /* {{{ ResourceBundle_ctor */
resourcebundle_ctor(INTERNAL_FUNCTION_PARAMETERS,zend_error_handling * error_handling,bool * error_handling_replaced)77 static int resourcebundle_ctor(INTERNAL_FUNCTION_PARAMETERS, zend_error_handling *error_handling, bool *error_handling_replaced)
78 {
79 	const char *bundlename;
80 	size_t		bundlename_len = 0;
81 	const char *locale;
82 	size_t		locale_len = 0;
83 	bool	fallback = 1;
84 
85 	zval                  *object = return_value;
86 	ResourceBundle_object *rb = Z_INTL_RESOURCEBUNDLE_P( object );
87 
88 	intl_error_reset( NULL );
89 
90 	if( zend_parse_parameters( ZEND_NUM_ARGS(), "s!s!|b",
91 		&locale, &locale_len, &bundlename, &bundlename_len, &fallback ) == FAILURE )
92 	{
93 		return FAILURE;
94 	}
95 
96 	if (error_handling != NULL) {
97 		zend_replace_error_handling(EH_THROW, IntlException_ce_ptr, error_handling);
98 		*error_handling_replaced = 1;
99 	}
100 
101 	if (rb->me) {
102 		zend_throw_error(NULL, "ResourceBundle object is already constructed");
103 		return FAILURE;
104 	}
105 
106 	INTL_CHECK_LOCALE_LEN_OR_FAILURE(locale_len);
107 
108 	if (locale == NULL) {
109 		locale = intl_locale_get_default();
110 	}
111 
112 	if (bundlename_len >= MAXPATHLEN) {
113 		zend_argument_value_error(2, "is too long");
114 		return FAILURE;
115 	}
116 
117 	if (fallback) {
118 		rb->me = ures_open(bundlename, locale, &INTL_DATA_ERROR_CODE(rb));
119 	} else {
120 		rb->me = ures_openDirect(bundlename, locale, &INTL_DATA_ERROR_CODE(rb));
121 	}
122 
123 	INTL_CTOR_CHECK_STATUS(rb, "resourcebundle_ctor: Cannot load libICU resource bundle");
124 
125 	if (!fallback && (INTL_DATA_ERROR_CODE(rb) == U_USING_FALLBACK_WARNING ||
126 			INTL_DATA_ERROR_CODE(rb) == U_USING_DEFAULT_WARNING)) {
127 		char *pbuf;
128 		intl_errors_set_code(NULL, INTL_DATA_ERROR_CODE(rb));
129 		spprintf(&pbuf, 0, "resourcebundle_ctor: Cannot load libICU resource "
130 				"'%s' without fallback from %s to %s",
131 				bundlename ? bundlename : "(default data)", locale,
132 				ures_getLocaleByType(
133 					rb->me, ULOC_ACTUAL_LOCALE, &INTL_DATA_ERROR_CODE(rb)));
134 		intl_errors_set_custom_msg(INTL_DATA_ERROR_P(rb), pbuf, 1);
135 		efree(pbuf);
136 		return FAILURE;
137 	}
138 
139 	return SUCCESS;
140 }
141 /* }}} */
142 
143 /* {{{ ResourceBundle object constructor */
PHP_METHOD(ResourceBundle,__construct)144 PHP_METHOD( ResourceBundle, __construct )
145 {
146 	zend_error_handling error_handling;
147 	bool error_handling_replaced = 0;
148 
149 	return_value = ZEND_THIS;
150 	if (resourcebundle_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, &error_handling, &error_handling_replaced) == FAILURE) {
151 		if (!EG(exception)) {
152 			zend_throw_exception(IntlException_ce_ptr, "Constructor failed", 0);
153 		}
154 	}
155 	if (error_handling_replaced) {
156 		zend_restore_error_handling(&error_handling);
157 	}
158 }
159 /* }}} */
160 
161 /* {{{ */
PHP_FUNCTION(resourcebundle_create)162 PHP_FUNCTION( resourcebundle_create )
163 {
164 	object_init_ex( return_value, ResourceBundle_ce_ptr );
165 	if (resourcebundle_ctor(INTERNAL_FUNCTION_PARAM_PASSTHRU, NULL, NULL) == FAILURE) {
166 		zval_ptr_dtor(return_value);
167 		RETURN_NULL();
168 	}
169 }
170 /* }}} */
171 
172 /* {{{ resourcebundle_array_fetch */
resourcebundle_array_fetch(zend_object * object,zval * offset,zval * return_value,int fallback)173 static void resourcebundle_array_fetch(zend_object *object, zval *offset, zval *return_value, int fallback)
174 {
175 	int32_t     meindex = 0;
176 	char *      mekey = NULL;
177 	bool        is_numeric = 0;
178 	char        *pbuf;
179 	ResourceBundle_object *rb;
180 
181 	rb = php_intl_resourcebundle_fetch_object(object);
182 	intl_error_reset(NULL);
183 	intl_error_reset(INTL_DATA_ERROR_P(rb));
184 
185 	if(Z_TYPE_P(offset) == IS_LONG) {
186 		is_numeric = 1;
187 		meindex = (int32_t)Z_LVAL_P(offset);
188 		rb->child = ures_getByIndex( rb->me, meindex, rb->child, &INTL_DATA_ERROR_CODE(rb) );
189 	} else if(Z_TYPE_P(offset) == IS_STRING) {
190 		mekey = Z_STRVAL_P(offset);
191 		rb->child = ures_getByKey(rb->me, mekey, rb->child, &INTL_DATA_ERROR_CODE(rb) );
192 	} else {
193 		intl_errors_set(INTL_DATA_ERROR_P(rb), U_ILLEGAL_ARGUMENT_ERROR,
194 			"resourcebundle_get: index should be integer or string", 0);
195 		RETURN_NULL();
196 	}
197 
198 	intl_error_set_code( NULL, INTL_DATA_ERROR_CODE(rb) );
199 	if (U_FAILURE(INTL_DATA_ERROR_CODE(rb))) {
200 		if (is_numeric) {
201 			spprintf( &pbuf, 0, "Cannot load resource element %d", meindex );
202 		} else {
203 			spprintf( &pbuf, 0, "Cannot load resource element '%s'", mekey );
204 		}
205 		intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1 );
206 		efree(pbuf);
207 		RETURN_NULL();
208 	}
209 
210 	if (!fallback && (INTL_DATA_ERROR_CODE(rb) == U_USING_FALLBACK_WARNING || INTL_DATA_ERROR_CODE(rb) == U_USING_DEFAULT_WARNING)) {
211 		UErrorCode icuerror;
212 		const char * locale = ures_getLocaleByType( rb->me, ULOC_ACTUAL_LOCALE, &icuerror );
213 		if (is_numeric) {
214 			spprintf( &pbuf, 0, "Cannot load element %d without fallback from to %s", meindex, locale );
215 		} else {
216 			spprintf( &pbuf, 0, "Cannot load element '%s' without fallback from to %s", mekey, locale );
217 		}
218 		intl_errors_set_custom_msg( INTL_DATA_ERROR_P(rb), pbuf, 1 );
219 		efree(pbuf);
220 		RETURN_NULL();
221 	}
222 
223 	resourcebundle_extract_value( return_value, rb );
224 }
225 /* }}} */
226 
227 /* {{{ resourcebundle_array_get */
resourcebundle_array_get(zend_object * object,zval * offset,int type,zval * rv)228 zval *resourcebundle_array_get(zend_object *object, zval *offset, int type, zval *rv)
229 {
230 	if(offset == NULL) {
231 		php_error( E_ERROR, "Cannot apply [] to ResourceBundle object" );
232 	}
233 	ZVAL_NULL(rv);
234 	resourcebundle_array_fetch(object, offset, rv, 1);
235 	return rv;
236 }
237 /* }}} */
238 
239 /* {{{ Get resource identified by numerical index or key name. */
PHP_FUNCTION(resourcebundle_get)240 PHP_FUNCTION( resourcebundle_get )
241 {
242 	bool   fallback = 1;
243 	zval *		offset;
244 	zval *      object;
245 
246 	if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oz|b",	&object, ResourceBundle_ce_ptr, &offset, &fallback ) == FAILURE) {
247 		RETURN_THROWS();
248 	}
249 
250 	resourcebundle_array_fetch(Z_OBJ_P(object), offset, return_value, fallback);
251 }
252 /* }}} */
253 
254 /* {{{ resourcebundle_array_count */
resourcebundle_array_count(zend_object * object,zend_long * count)255 int resourcebundle_array_count(zend_object *object, zend_long *count)
256 {
257 	ResourceBundle_object *rb = php_intl_resourcebundle_fetch_object(object);
258 
259 	if (rb->me == NULL) {
260 		intl_errors_set(&rb->error, U_ILLEGAL_ARGUMENT_ERROR,
261 				"Found unconstructed ResourceBundle", 0);
262 		return 0;
263 	}
264 
265 	*count = ures_getSize( rb->me );
266 
267 	return SUCCESS;
268 }
269 /* }}} */
270 
271 /* {{{ Get resources count */
PHP_FUNCTION(resourcebundle_count)272 PHP_FUNCTION( resourcebundle_count )
273 {
274 	int32_t                len;
275 	RESOURCEBUNDLE_METHOD_INIT_VARS;
276 
277 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "O", &object, ResourceBundle_ce_ptr ) == FAILURE ) {
278 		RETURN_THROWS();
279 	}
280 
281 	RESOURCEBUNDLE_METHOD_FETCH_OBJECT;
282 
283 	len = ures_getSize( rb->me );
284 	RETURN_LONG( len );
285 }
286 
287 /* {{{ Get available locales from ResourceBundle name */
PHP_FUNCTION(resourcebundle_locales)288 PHP_FUNCTION( resourcebundle_locales )
289 {
290 	char * bundlename;
291 	size_t    bundlename_len = 0;
292 	const char * entry;
293 	int entry_len;
294 	UEnumeration *icuenum;
295 	UErrorCode   icuerror = U_ZERO_ERROR;
296 
297 	intl_errors_reset( NULL );
298 
299 	if( zend_parse_parameters(ZEND_NUM_ARGS(), "s", &bundlename, &bundlename_len ) == FAILURE )
300 	{
301 		RETURN_THROWS();
302 	}
303 
304 	if (bundlename_len >= MAXPATHLEN) {
305 		zend_argument_value_error(1, "is too long");
306 		RETURN_THROWS();
307 	}
308 
309 	if(bundlename_len == 0) {
310 		// fetch default locales list
311 		bundlename = NULL;
312 	}
313 
314 	icuenum = ures_openAvailableLocales( bundlename, &icuerror );
315 	INTL_CHECK_STATUS(icuerror, "Cannot fetch locales list");
316 
317 	uenum_reset( icuenum, &icuerror );
318 	INTL_CHECK_STATUS(icuerror, "Cannot iterate locales list");
319 
320 	array_init( return_value );
321 	while ((entry = uenum_next( icuenum, &entry_len, &icuerror ))) {
322 		add_next_index_stringl( return_value, (char *) entry, entry_len);
323 	}
324 	uenum_close( icuenum );
325 }
326 /* }}} */
327 
328 /* {{{ Get text description for ResourceBundle's last error code. */
PHP_FUNCTION(resourcebundle_get_error_code)329 PHP_FUNCTION( resourcebundle_get_error_code )
330 {
331 	RESOURCEBUNDLE_METHOD_INIT_VARS;
332 
333 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "O",
334 		&object, ResourceBundle_ce_ptr ) == FAILURE )
335 	{
336 		RETURN_THROWS();
337 	}
338 
339 	rb = Z_INTL_RESOURCEBUNDLE_P( object );
340 
341 	RETURN_LONG(INTL_DATA_ERROR_CODE(rb));
342 }
343 /* }}} */
344 
345 /* {{{ Get text description for ResourceBundle's last error. */
PHP_FUNCTION(resourcebundle_get_error_message)346 PHP_FUNCTION( resourcebundle_get_error_message )
347 {
348 	zend_string* message = NULL;
349 	RESOURCEBUNDLE_METHOD_INIT_VARS;
350 
351 	if( zend_parse_method_parameters( ZEND_NUM_ARGS(), getThis(), "O",
352 		&object, ResourceBundle_ce_ptr ) == FAILURE )
353 	{
354 		RETURN_THROWS();
355 	}
356 
357 	rb = Z_INTL_RESOURCEBUNDLE_P( object );
358 	message = intl_error_get_message(INTL_DATA_ERROR_P(rb));
359 	RETURN_STR(message);
360 }
361 /* }}} */
362 
PHP_METHOD(ResourceBundle,getIterator)363 PHP_METHOD(ResourceBundle, getIterator) {
364 	if (zend_parse_parameters_none() == FAILURE) {
365 		return;
366 	}
367 
368 	zend_create_internal_iterator_zval(return_value, ZEND_THIS);
369 }
370 
371 /* {{{ resourcebundle_register_class
372  * Initialize 'ResourceBundle' class
373  */
resourcebundle_register_class(void)374 void resourcebundle_register_class( void )
375 {
376 	ResourceBundle_ce_ptr = register_class_ResourceBundle(zend_ce_aggregate, zend_ce_countable);
377 	ResourceBundle_ce_ptr->create_object = ResourceBundle_object_create;
378 	ResourceBundle_ce_ptr->get_iterator = resourcebundle_get_iterator;
379 
380 	ResourceBundle_object_handlers = std_object_handlers;
381 	ResourceBundle_object_handlers.offset = XtOffsetOf(ResourceBundle_object, zend);
382 	ResourceBundle_object_handlers.clone_obj	  = NULL; /* ICU ResourceBundle has no clone implementation */
383 	ResourceBundle_object_handlers.free_obj = ResourceBundle_object_free;
384 	ResourceBundle_object_handlers.read_dimension = resourcebundle_array_get;
385 	ResourceBundle_object_handlers.count_elements = resourcebundle_array_count;
386 }
387 /* }}} */
388