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: Sara Golemon <pollita@php.net> |
12 +----------------------------------------------------------------------+
13 */
14
15 #include "converter.h"
16 #include "zend_exceptions.h"
17
18 #include <unicode/utypes.h>
19 #include <unicode/utf8.h>
20 #include <unicode/utf16.h>
21 #include <unicode/ucnv.h>
22 #include <unicode/ustring.h>
23
24 #include "../intl_error.h"
25 #include "../intl_common.h"
26 #include "converter_arginfo.h"
27
28 typedef struct _php_converter_object {
29 UConverter *src, *dest;
30 zend_fcall_info to_cb, from_cb;
31 zend_fcall_info_cache to_cache, from_cache;
32 intl_error error;
33 zend_object obj;
34 } php_converter_object;
35
36
php_converter_fetch_object(zend_object * obj)37 static inline php_converter_object *php_converter_fetch_object(zend_object *obj) {
38 return (php_converter_object *)((char*)(obj) - XtOffsetOf(php_converter_object, obj));
39 }
40 #define Z_INTL_CONVERTER_P(zv) php_converter_fetch_object(Z_OBJ_P(zv))
41
42 static zend_class_entry *php_converter_ce;
43 static zend_object_handlers php_converter_object_handlers;
44
45 #define CONV_GET(pzv) (Z_INTL_CONVERTER_P((pzv)))
46 #define THROW_UFAILURE(obj, fname, error) php_converter_throw_failure(obj, error, \
47 fname "() returned error " ZEND_LONG_FMT ": %s", (zend_long)error, u_errorName(error))
48
49 /* {{{ php_converter_throw_failure */
php_converter_throw_failure(php_converter_object * objval,UErrorCode error,const char * format,...)50 static inline void php_converter_throw_failure(php_converter_object *objval, UErrorCode error, const char *format, ...) {
51 intl_error *err = objval ? &(objval->error) : NULL;
52 char message[1024];
53 va_list vargs;
54
55 va_start(vargs, format);
56 vsnprintf(message, sizeof(message), format, vargs);
57 va_end(vargs);
58
59 intl_errors_set(err, error, message, 1);
60 }
61 /* }}} */
62
63 /* {{{ php_converter_default_callback */
php_converter_default_callback(zval * return_value,zval * zobj,zend_long reason,zval * error)64 static void php_converter_default_callback(zval *return_value, zval *zobj, zend_long reason, zval *error) {
65 /* Basic functionality so children can call parent::toUCallback() */
66 switch (reason) {
67 case UCNV_UNASSIGNED:
68 case UCNV_ILLEGAL:
69 case UCNV_IRREGULAR:
70 {
71 php_converter_object *objval = (php_converter_object*)CONV_GET(zobj);
72 char chars[127];
73 int8_t chars_len = sizeof(chars);
74 UErrorCode uerror = U_ZERO_ERROR;
75 if(!objval->src) {
76 php_converter_throw_failure(objval, U_INVALID_STATE_ERROR, "Source Converter has not been initialized yet");
77 chars[0] = 0x1A;
78 chars[1] = 0;
79 chars_len = 1;
80 ZEND_TRY_ASSIGN_REF_LONG(error, U_INVALID_STATE_ERROR);
81 RETVAL_STRINGL(chars, chars_len);
82 return;
83 }
84
85 /* Yes, this is fairly wasteful at first glance,
86 * but considering that the alternative is to store
87 * what's sent into setSubstChars() and the fact
88 * that this is an extremely unlikely codepath
89 * I'd rather take the CPU hit here, than waste time
90 * storing a value I'm unlikely to use.
91 */
92 ucnv_getSubstChars(objval->src, chars, &chars_len, &uerror);
93 if (U_FAILURE(uerror)) {
94 THROW_UFAILURE(objval, "ucnv_getSubstChars", uerror);
95 chars[0] = 0x1A;
96 chars[1] = 0;
97 chars_len = 1;
98 }
99 ZEND_TRY_ASSIGN_REF_LONG(error, uerror);
100 RETVAL_STRINGL(chars, chars_len);
101 }
102 }
103 }
104 /* }}} */
105
106 /* {{{ */
PHP_METHOD(UConverter,toUCallback)107 PHP_METHOD(UConverter, toUCallback) {
108 zend_long reason;
109 zend_string *source, *codeUnits;
110 zval *error;
111
112 if (zend_parse_parameters(ZEND_NUM_ARGS(), "lSSz",
113 &reason, &source, &codeUnits, &error) == FAILURE) {
114 RETURN_THROWS();
115 }
116
117 php_converter_default_callback(return_value, ZEND_THIS, reason, error);
118 }
119 /* }}} */
120
121 /* {{{ */
PHP_METHOD(UConverter,fromUCallback)122 PHP_METHOD(UConverter, fromUCallback) {
123 zend_long reason;
124 zval *source, *error;
125 zend_long codePoint;
126
127 if (zend_parse_parameters(ZEND_NUM_ARGS(), "lalz",
128 &reason, &source, &codePoint, &error) == FAILURE) {
129 RETURN_THROWS();
130 }
131
132 php_converter_default_callback(return_value, ZEND_THIS, reason, error);
133 }
134 /* }}} */
135
136 /* {{{ php_converter_check_limits */
php_converter_check_limits(php_converter_object * objval,zend_long available,zend_long needed)137 static inline bool php_converter_check_limits(php_converter_object *objval, zend_long available, zend_long needed) {
138 if (available < needed) {
139 php_converter_throw_failure(objval, U_BUFFER_OVERFLOW_ERROR, "Buffer overrun " ZEND_LONG_FMT " bytes needed, " ZEND_LONG_FMT " available", needed, available);
140 return 0;
141 }
142 return 1;
143 }
144 /* }}} */
145
146 #define TARGET_CHECK(cnvargs, needed) php_converter_check_limits(objval, cnvargs->targetLimit - cnvargs->target, needed)
147
148 /* {{{ php_converter_append_toUnicode_target */
php_converter_append_toUnicode_target(zval * val,UConverterToUnicodeArgs * args,php_converter_object * objval)149 static void php_converter_append_toUnicode_target(zval *val, UConverterToUnicodeArgs *args, php_converter_object *objval) {
150 switch (Z_TYPE_P(val)) {
151 case IS_NULL:
152 /* Code unit is being skipped */
153 return;
154 case IS_LONG:
155 {
156 zend_long lval = Z_LVAL_P(val);
157 if ((lval < 0) || (lval > 0x10FFFF)) {
158 php_converter_throw_failure(objval, U_ILLEGAL_ARGUMENT_ERROR, "Invalid codepoint U+%04lx", lval);
159 return;
160 }
161 if (lval > 0xFFFF) {
162 /* Supplemental planes U+010000 - U+10FFFF */
163 if (TARGET_CHECK(args, 2)) {
164 /* TODO: Find the ICU call which does this properly */
165 *(args->target++) = (UChar)(((lval - 0x10000) >> 10) | 0xD800);
166 *(args->target++) = (UChar)(((lval - 0x10000) & 0x3FF) | 0xDC00);
167 }
168 return;
169 }
170 /* Non-suggogate BMP codepoint */
171 if (TARGET_CHECK(args, 1)) {
172 *(args->target++) = (UChar)lval;
173 }
174 return;
175 }
176 case IS_STRING:
177 {
178 const char *strval = Z_STRVAL_P(val);
179 int i = 0, strlen = Z_STRLEN_P(val);
180
181 while((i != strlen) && TARGET_CHECK(args, 1)) {
182 UChar c;
183 U8_NEXT(strval, i, strlen, c);
184 *(args->target++) = c;
185 }
186 return;
187 }
188 case IS_ARRAY:
189 {
190 HashTable *ht = Z_ARRVAL_P(val);
191 zval *tmpzval;
192
193 ZEND_HASH_FOREACH_VAL(ht, tmpzval) {
194 php_converter_append_toUnicode_target(tmpzval, args, objval);
195 } ZEND_HASH_FOREACH_END();
196 return;
197 }
198 default:
199 php_converter_throw_failure(objval, U_ILLEGAL_ARGUMENT_ERROR,
200 "toUCallback() specified illegal type for substitution character");
201 }
202 }
203 /* }}} */
204
205 /* {{{ php_converter_to_u_callback */
php_converter_to_u_callback(const void * context,UConverterToUnicodeArgs * args,const char * codeUnits,int32_t length,UConverterCallbackReason reason,UErrorCode * pErrorCode)206 static void php_converter_to_u_callback(const void *context,
207 UConverterToUnicodeArgs *args,
208 const char *codeUnits, int32_t length,
209 UConverterCallbackReason reason,
210 UErrorCode *pErrorCode) {
211 php_converter_object *objval = (php_converter_object*)context;
212 zval retval;
213 zval zargs[4];
214
215 ZVAL_LONG(&zargs[0], reason);
216 if (args->source) {
217 ZVAL_STRINGL(&zargs[1], args->source, args->sourceLimit - args->source);
218 } else {
219 ZVAL_EMPTY_STRING(&zargs[1]);
220 }
221 if (codeUnits) {
222 ZVAL_STRINGL(&zargs[2], codeUnits, length);
223 } else {
224 ZVAL_EMPTY_STRING(&zargs[2]);
225 }
226 ZVAL_LONG(&zargs[3], *pErrorCode);
227 ZVAL_MAKE_REF(&zargs[3]);
228
229 objval->to_cb.param_count = 4;
230 objval->to_cb.params = zargs;
231 objval->to_cb.retval = &retval;
232 if (zend_call_function(&(objval->to_cb), &(objval->to_cache)) == FAILURE) {
233 /* Unlikely */
234 php_converter_throw_failure(objval, U_INTERNAL_PROGRAM_ERROR, "Unexpected failure calling toUCallback()");
235 } else if (!Z_ISUNDEF(retval)) {
236 php_converter_append_toUnicode_target(&retval, args, objval);
237 zval_ptr_dtor(&retval);
238 }
239
240 if (Z_TYPE(zargs[3]) == IS_LONG) {
241 *pErrorCode = Z_LVAL(zargs[3]);
242 } else if (Z_ISREF(zargs[3]) && Z_TYPE_P(Z_REFVAL(zargs[3])) == IS_LONG) {
243 *pErrorCode = Z_LVAL_P(Z_REFVAL(zargs[3]));
244 }
245
246 zval_ptr_dtor(&zargs[0]);
247 zval_ptr_dtor(&zargs[1]);
248 zval_ptr_dtor(&zargs[2]);
249 zval_ptr_dtor(&zargs[3]);
250 }
251 /* }}} */
252
253 /* {{{ php_converter_append_fromUnicode_target */
php_converter_append_fromUnicode_target(zval * val,UConverterFromUnicodeArgs * args,php_converter_object * objval)254 static void php_converter_append_fromUnicode_target(zval *val, UConverterFromUnicodeArgs *args, php_converter_object *objval) {
255 switch (Z_TYPE_P(val)) {
256 case IS_NULL:
257 /* Ignore */
258 return;
259 case IS_LONG:
260 if (TARGET_CHECK(args, 1)) {
261 *(args->target++) = Z_LVAL_P(val);
262 }
263 return;
264 case IS_STRING:
265 {
266 size_t vallen = Z_STRLEN_P(val);
267 if (TARGET_CHECK(args, vallen)) {
268 args->target = zend_mempcpy(args->target, Z_STRVAL_P(val), vallen);
269 }
270 return;
271 }
272 case IS_ARRAY:
273 {
274 HashTable *ht = Z_ARRVAL_P(val);
275 zval *tmpzval;
276 ZEND_HASH_FOREACH_VAL(ht, tmpzval) {
277 php_converter_append_fromUnicode_target(tmpzval, args, objval);
278 } ZEND_HASH_FOREACH_END();
279 return;
280 }
281 default:
282 php_converter_throw_failure(objval, U_ILLEGAL_ARGUMENT_ERROR, "fromUCallback() specified illegal type for substitution character");
283 }
284 }
285 /* }}} */
286
287 /* {{{ php_converter_from_u_callback */
php_converter_from_u_callback(const void * context,UConverterFromUnicodeArgs * args,const UChar * codeUnits,int32_t length,UChar32 codePoint,UConverterCallbackReason reason,UErrorCode * pErrorCode)288 static void php_converter_from_u_callback(const void *context,
289 UConverterFromUnicodeArgs *args,
290 const UChar *codeUnits, int32_t length, UChar32 codePoint,
291 UConverterCallbackReason reason,
292 UErrorCode *pErrorCode) {
293 php_converter_object *objval = (php_converter_object*)context;
294 zval retval;
295 zval zargs[4];
296 int i;
297
298 ZVAL_LONG(&zargs[0], reason);
299 array_init(&zargs[1]);
300 i = 0;
301 while (i < length) {
302 UChar32 c;
303 U16_NEXT(codeUnits, i, length, c);
304 add_next_index_long(&zargs[1], c);
305 }
306 ZVAL_LONG(&zargs[2], codePoint);
307 ZVAL_LONG(&zargs[3], *pErrorCode);
308 ZVAL_MAKE_REF(&zargs[3]);
309
310 objval->from_cb.param_count = 4;
311 objval->from_cb.params = zargs;
312 objval->from_cb.retval = &retval;
313 if (zend_call_function(&(objval->from_cb), &(objval->from_cache)) == FAILURE) {
314 /* Unlikely */
315 php_converter_throw_failure(objval, U_INTERNAL_PROGRAM_ERROR, "Unexpected failure calling fromUCallback()");
316 } else if (!Z_ISUNDEF(retval)) {
317 php_converter_append_fromUnicode_target(&retval, args, objval);
318 zval_ptr_dtor(&retval);
319 }
320
321 if (Z_TYPE(zargs[3]) == IS_LONG) {
322 *pErrorCode = Z_LVAL(zargs[3]);
323 } else if (Z_ISREF(zargs[3]) && Z_TYPE_P(Z_REFVAL(zargs[3])) == IS_LONG) {
324 *pErrorCode = Z_LVAL_P(Z_REFVAL(zargs[3]));
325 }
326
327 zval_ptr_dtor(&zargs[0]);
328 zval_ptr_dtor(&zargs[1]);
329 zval_ptr_dtor(&zargs[2]);
330 zval_ptr_dtor(&zargs[3]);
331 }
332 /* }}} */
333
334 /* {{{ php_converter_set_callbacks */
php_converter_set_callbacks(php_converter_object * objval,UConverter * cnv)335 static inline bool php_converter_set_callbacks(php_converter_object *objval, UConverter *cnv) {
336 bool ret = 1;
337 UErrorCode error = U_ZERO_ERROR;
338
339 if (objval->obj.ce == php_converter_ce) {
340 /* Short-circuit having to go through method calls and data marshalling
341 * when we're using default behavior
342 */
343 return 1;
344 }
345
346 ucnv_setToUCallBack(cnv, (UConverterToUCallback)php_converter_to_u_callback, (const void*)objval,
347 NULL, NULL, &error);
348 if (U_FAILURE(error)) {
349 THROW_UFAILURE(objval, "ucnv_setToUCallBack", error);
350 ret = 0;
351 }
352
353 error = U_ZERO_ERROR;
354 ucnv_setFromUCallBack(cnv, (UConverterFromUCallback)php_converter_from_u_callback, (const void*)objval,
355 NULL, NULL, &error);
356 if (U_FAILURE(error)) {
357 THROW_UFAILURE(objval, "ucnv_setFromUCallBack", error);
358 ret = 0;
359 }
360 return ret;
361 }
362 /* }}} */
363
364 /* {{{ php_converter_set_encoding */
php_converter_set_encoding(php_converter_object * objval,UConverter ** pcnv,const char * enc,size_t enc_len)365 static bool php_converter_set_encoding(php_converter_object *objval,
366 UConverter **pcnv,
367 const char *enc, size_t enc_len) {
368 UErrorCode error = U_ZERO_ERROR;
369 UConverter *cnv = ucnv_open(enc, &error);
370
371 if (error == U_AMBIGUOUS_ALIAS_WARNING) {
372 UErrorCode getname_error = U_ZERO_ERROR;
373 const char *actual_encoding = ucnv_getName(cnv, &getname_error);
374 if (U_FAILURE(getname_error)) {
375 /* Should never happen */
376 actual_encoding = "(unknown)";
377 }
378 php_error_docref(NULL, E_WARNING, "Ambiguous encoding specified, using %s", actual_encoding);
379 } else if (U_FAILURE(error)) {
380 if (objval) {
381 THROW_UFAILURE(objval, "ucnv_open", error);
382 } else {
383 php_error_docref(NULL, E_WARNING, "Error setting encoding: %d - %s", (int)error, u_errorName(error));
384 }
385 return 0;
386 }
387
388 if (objval && !php_converter_set_callbacks(objval, cnv)) {
389 return 0;
390 }
391
392 if (*pcnv) {
393 ucnv_close(*pcnv);
394 }
395 *pcnv = cnv;
396 return 1;
397 }
398 /* }}} */
399
400 /* {{{ php_converter_do_set_encoding */
php_converter_do_set_encoding(UConverter ** pcnv,INTERNAL_FUNCTION_PARAMETERS)401 static void php_converter_do_set_encoding(UConverter **pcnv, INTERNAL_FUNCTION_PARAMETERS) {
402 php_converter_object *objval = CONV_GET(ZEND_THIS);
403 char *enc;
404 size_t enc_len;
405
406 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &enc, &enc_len) == FAILURE) {
407 RETURN_THROWS();
408 }
409 intl_errors_reset(&objval->error);
410
411 RETURN_BOOL(php_converter_set_encoding(objval, pcnv, enc, enc_len));
412 }
413 /* }}} */
414
415 /* {{{ */
PHP_METHOD(UConverter,setSourceEncoding)416 PHP_METHOD(UConverter, setSourceEncoding) {
417 php_converter_object *objval = CONV_GET(ZEND_THIS);
418 php_converter_do_set_encoding(&(objval->src), INTERNAL_FUNCTION_PARAM_PASSTHRU);
419 }
420 /* }}} */
421
422 /* {{{ */
PHP_METHOD(UConverter,setDestinationEncoding)423 PHP_METHOD(UConverter, setDestinationEncoding) {
424 php_converter_object *objval = CONV_GET(ZEND_THIS);
425 php_converter_do_set_encoding(&(objval->dest), INTERNAL_FUNCTION_PARAM_PASSTHRU);
426 }
427 /* }}} */
428
429 /* {{{ php_converter_do_get_encoding */
php_converter_do_get_encoding(php_converter_object * objval,UConverter * cnv,INTERNAL_FUNCTION_PARAMETERS)430 static void php_converter_do_get_encoding(php_converter_object *objval, UConverter *cnv, INTERNAL_FUNCTION_PARAMETERS) {
431 const char *name;
432
433 if (zend_parse_parameters_none() == FAILURE) {
434 RETURN_THROWS();
435 }
436
437 intl_errors_reset(&objval->error);
438
439 if (!cnv) {
440 RETURN_NULL();
441 }
442
443 name = ucnv_getName(cnv, &objval->error.code);
444 if (U_FAILURE(objval->error.code)) {
445 THROW_UFAILURE(objval, "ucnv_getName()", objval->error.code);
446 RETURN_FALSE;
447 }
448
449 RETURN_STRING(name);
450 }
451 /* }}} */
452
453 /* {{{ */
PHP_METHOD(UConverter,getSourceEncoding)454 PHP_METHOD(UConverter, getSourceEncoding) {
455 php_converter_object *objval = CONV_GET(ZEND_THIS);
456 php_converter_do_get_encoding(objval, objval->src, INTERNAL_FUNCTION_PARAM_PASSTHRU);
457 }
458 /* }}} */
459
460 /* {{{ */
PHP_METHOD(UConverter,getDestinationEncoding)461 PHP_METHOD(UConverter, getDestinationEncoding) {
462 php_converter_object *objval = CONV_GET(ZEND_THIS);
463 php_converter_do_get_encoding(objval, objval->dest, INTERNAL_FUNCTION_PARAM_PASSTHRU);
464 }
465 /* }}} */
466
467 /* {{{ php_converter_do_get_type */
php_converter_do_get_type(php_converter_object * objval,UConverter * cnv,INTERNAL_FUNCTION_PARAMETERS)468 static void php_converter_do_get_type(php_converter_object *objval, UConverter *cnv, INTERNAL_FUNCTION_PARAMETERS) {
469 UConverterType t;
470
471 if (zend_parse_parameters_none() == FAILURE) {
472 RETURN_THROWS();
473 }
474 intl_errors_reset(&objval->error);
475
476 if (!cnv) {
477 RETURN_NULL();
478 }
479
480 t = ucnv_getType(cnv);
481 if (U_FAILURE(objval->error.code)) {
482 THROW_UFAILURE(objval, "ucnv_getType", objval->error.code);
483 RETURN_FALSE;
484 }
485
486 RETURN_LONG(t);
487 }
488 /* }}} */
489
490 /* {{{ */
PHP_METHOD(UConverter,getSourceType)491 PHP_METHOD(UConverter, getSourceType) {
492 php_converter_object *objval = CONV_GET(ZEND_THIS);
493 php_converter_do_get_type(objval, objval->src, INTERNAL_FUNCTION_PARAM_PASSTHRU);
494 }
495 /* }}} */
496
497 /* {{{ */
PHP_METHOD(UConverter,getDestinationType)498 PHP_METHOD(UConverter, getDestinationType) {
499 php_converter_object *objval = CONV_GET(ZEND_THIS);
500 php_converter_do_get_type(objval, objval->dest, INTERNAL_FUNCTION_PARAM_PASSTHRU);
501 }
502 /* }}} */
503
504 /* {{{ php_converter_resolve_callback */
php_converter_resolve_callback(zval * zobj,php_converter_object * objval,const char * callback_name,zend_fcall_info * finfo,zend_fcall_info_cache * fcache)505 static void php_converter_resolve_callback(zval *zobj,
506 php_converter_object *objval,
507 const char *callback_name,
508 zend_fcall_info *finfo,
509 zend_fcall_info_cache *fcache) {
510 char *errstr = NULL;
511 zval caller;
512
513 array_init(&caller);
514 Z_ADDREF_P(zobj);
515 add_index_zval(&caller, 0, zobj);
516 add_index_string(&caller, 1, callback_name);
517 if (zend_fcall_info_init(&caller, 0, finfo, fcache, NULL, &errstr) == FAILURE) {
518 php_converter_throw_failure(objval, U_INTERNAL_PROGRAM_ERROR, "Error setting converter callback: %s", errstr);
519 }
520 zend_array_destroy(Z_ARR(caller));
521 ZVAL_UNDEF(&finfo->function_name);
522 if (errstr) {
523 efree(errstr);
524 }
525 }
526 /* }}} */
527
528 /* {{{ */
PHP_METHOD(UConverter,__construct)529 PHP_METHOD(UConverter, __construct) {
530 php_converter_object *objval = CONV_GET(ZEND_THIS);
531 char *src = "utf-8";
532 size_t src_len = sizeof("utf-8") - 1;
533 char *dest = src;
534 size_t dest_len = src_len;
535
536 intl_error_reset(NULL);
537
538 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!s!", &dest, &dest_len, &src, &src_len) == FAILURE) {
539 RETURN_THROWS();
540 }
541
542 php_converter_set_encoding(objval, &(objval->src), src, src_len );
543 php_converter_set_encoding(objval, &(objval->dest), dest, dest_len);
544 php_converter_resolve_callback(ZEND_THIS, objval, "toUCallback", &(objval->to_cb), &(objval->to_cache));
545 php_converter_resolve_callback(ZEND_THIS, objval, "fromUCallback", &(objval->from_cb), &(objval->from_cache));
546 }
547 /* }}} */
548
549 /* {{{ */
PHP_METHOD(UConverter,setSubstChars)550 PHP_METHOD(UConverter, setSubstChars) {
551 php_converter_object *objval = CONV_GET(ZEND_THIS);
552 char *chars;
553 size_t chars_len;
554 int ret = 1;
555
556 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &chars, &chars_len) == FAILURE) {
557 RETURN_THROWS();
558 }
559 intl_errors_reset(&objval->error);
560
561 if (objval->src) {
562 UErrorCode error = U_ZERO_ERROR;
563 ucnv_setSubstChars(objval->src, chars, chars_len, &error);
564 if (U_FAILURE(error)) {
565 THROW_UFAILURE(objval, "ucnv_setSubstChars", error);
566 ret = 0;
567 }
568 } else {
569 php_converter_throw_failure(objval, U_INVALID_STATE_ERROR, "Source Converter has not been initialized yet");
570 ret = 0;
571 }
572
573 if (objval->dest) {
574 UErrorCode error = U_ZERO_ERROR;
575 ucnv_setSubstChars(objval->dest, chars, chars_len, &error);
576 if (U_FAILURE(error)) {
577 THROW_UFAILURE(objval, "ucnv_setSubstChars", error);
578 ret = 0;
579 }
580 } else {
581 php_converter_throw_failure(objval, U_INVALID_STATE_ERROR, "Destination Converter has not been initialized yet");
582 ret = 0;
583 }
584
585 RETURN_BOOL(ret);
586 }
587 /* }}} */
588
589 /* {{{ */
PHP_METHOD(UConverter,getSubstChars)590 PHP_METHOD(UConverter, getSubstChars) {
591 php_converter_object *objval = CONV_GET(ZEND_THIS);
592 char chars[127];
593 int8_t chars_len = sizeof(chars);
594 UErrorCode error = U_ZERO_ERROR;
595
596 if (zend_parse_parameters_none() == FAILURE) {
597 RETURN_THROWS();
598 }
599 intl_errors_reset(&objval->error);
600
601 if (!objval->src) {
602 RETURN_NULL();
603 }
604
605 /* src and dest get the same subst chars set,
606 * so it doesn't really matter which one we read from
607 */
608 ucnv_getSubstChars(objval->src, chars, &chars_len, &error);
609 if (U_FAILURE(error)) {
610 THROW_UFAILURE(objval, "ucnv_getSubstChars", error);
611 RETURN_FALSE;
612 }
613
614 RETURN_STRINGL(chars, chars_len);
615 }
616 /* }}} */
617
618 /* {{{ php_converter_do_convert */
php_converter_do_convert(UConverter * dest_cnv,UConverter * src_cnv,const char * src,int32_t src_len,php_converter_object * objval)619 static zend_string* php_converter_do_convert(UConverter *dest_cnv,
620 UConverter *src_cnv, const char *src, int32_t src_len,
621 php_converter_object *objval
622 ) {
623 UErrorCode error = U_ZERO_ERROR;
624 int32_t temp_len, ret_len;
625 zend_string *ret;
626 UChar *temp;
627
628 if (!src_cnv || !dest_cnv) {
629 php_converter_throw_failure(objval, U_INVALID_STATE_ERROR,
630 "Internal converters not initialized");
631 return NULL;
632 }
633
634 /* Get necessary buffer size first */
635 temp_len = 1 + ucnv_toUChars(src_cnv, NULL, 0, src, src_len, &error);
636 if (U_FAILURE(error) && error != U_BUFFER_OVERFLOW_ERROR) {
637 THROW_UFAILURE(objval, "ucnv_toUChars", error);
638 return NULL;
639 }
640 temp = safe_emalloc(sizeof(UChar), temp_len, sizeof(UChar));
641
642 /* Convert to intermediate UChar* array */
643 error = U_ZERO_ERROR;
644 temp_len = ucnv_toUChars(src_cnv, temp, temp_len, src, src_len, &error);
645 if (U_FAILURE(error)) {
646 THROW_UFAILURE(objval, "ucnv_toUChars", error);
647 efree(temp);
648 return NULL;
649 }
650 temp[temp_len] = 0;
651
652 /* Get necessary output buffer size */
653 ret_len = ucnv_fromUChars(dest_cnv, NULL, 0, temp, temp_len, &error);
654 if (U_FAILURE(error) && error != U_BUFFER_OVERFLOW_ERROR) {
655 THROW_UFAILURE(objval, "ucnv_fromUChars", error);
656 efree(temp);
657 return NULL;
658 }
659
660 ret = zend_string_alloc(ret_len, 0);
661
662 /* Convert to final encoding */
663 error = U_ZERO_ERROR;
664 ZSTR_LEN(ret) = ucnv_fromUChars(dest_cnv, ZSTR_VAL(ret), ret_len+1, temp, temp_len, &error);
665 efree(temp);
666 if (U_FAILURE(error)) {
667 THROW_UFAILURE(objval, "ucnv_fromUChars", error);
668 zend_string_efree(ret);
669 return NULL;
670 }
671
672 return ret;
673 }
674 /* }}} */
675
676 /* {{{ */
677 #define UCNV_REASON_CASE(v) case (UCNV_ ## v) : RETURN_STRINGL( "REASON_" #v , sizeof( "REASON_" #v ) - 1);
PHP_METHOD(UConverter,reasonText)678 PHP_METHOD(UConverter, reasonText) {
679 zend_long reason;
680
681 if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &reason) == FAILURE) {
682 RETURN_THROWS();
683 }
684 intl_error_reset(NULL);
685
686 switch (reason) {
687 UCNV_REASON_CASE(UNASSIGNED)
688 UCNV_REASON_CASE(ILLEGAL)
689 UCNV_REASON_CASE(IRREGULAR)
690 UCNV_REASON_CASE(RESET)
691 UCNV_REASON_CASE(CLOSE)
692 UCNV_REASON_CASE(CLONE)
693 default:
694 zend_argument_value_error(1, "must be a UConverter::REASON_* constant");
695 RETURN_THROWS();
696 }
697 }
698 /* }}} */
699
700 /* {{{ */
PHP_METHOD(UConverter,convert)701 PHP_METHOD(UConverter, convert) {
702 php_converter_object *objval = CONV_GET(ZEND_THIS);
703 char *str;
704 size_t str_len;
705 zend_string *ret;
706 bool reverse = 0;
707
708 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|b",
709 &str, &str_len, &reverse) == FAILURE) {
710 RETURN_THROWS();
711 }
712 intl_errors_reset(&objval->error);
713
714 ret = php_converter_do_convert(reverse ? objval->src : objval->dest,
715 reverse ? objval->dest : objval->src,
716 str, str_len,
717 objval);
718 if (ret) {
719 RETURN_NEW_STR(ret);
720 } else {
721 RETURN_FALSE;
722 }
723 }
724 /* }}} */
725
726 /* {{{ */
PHP_METHOD(UConverter,transcode)727 PHP_METHOD(UConverter, transcode) {
728 char *str, *src, *dest;
729 size_t str_len, src_len, dest_len;
730 zval *options = NULL;
731 UConverter *src_cnv = NULL, *dest_cnv = NULL;
732
733 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|a!",
734 &str, &str_len, &dest, &dest_len, &src, &src_len, &options) == FAILURE) {
735 RETURN_THROWS();
736 }
737 intl_error_reset(NULL);
738
739 if (php_converter_set_encoding(NULL, &src_cnv, src, src_len) &&
740 php_converter_set_encoding(NULL, &dest_cnv, dest, dest_len)) {
741 zend_string *ret;
742 UErrorCode error = U_ZERO_ERROR;
743
744 if (options && zend_hash_num_elements(Z_ARRVAL_P(options))) {
745 zval *tmpzval;
746
747 if (U_SUCCESS(error) &&
748 (tmpzval = zend_hash_str_find(Z_ARRVAL_P(options), "from_subst", sizeof("from_subst") - 1)) != NULL &&
749 Z_TYPE_P(tmpzval) == IS_STRING) {
750 error = U_ZERO_ERROR;
751 ucnv_setSubstChars(src_cnv, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval) & 0x7F, &error);
752 }
753 if (U_SUCCESS(error) &&
754 (tmpzval = zend_hash_str_find(Z_ARRVAL_P(options), "to_subst", sizeof("to_subst") - 1)) != NULL &&
755 Z_TYPE_P(tmpzval) == IS_STRING) {
756 error = U_ZERO_ERROR;
757 ucnv_setSubstChars(dest_cnv, Z_STRVAL_P(tmpzval), Z_STRLEN_P(tmpzval) & 0x7F, &error);
758 }
759 }
760
761 if (U_SUCCESS(error) &&
762 (ret = php_converter_do_convert(dest_cnv, src_cnv, str, str_len, NULL)) != NULL) {
763 RETVAL_NEW_STR(ret);
764 }
765
766 if (U_FAILURE(error)) {
767 THROW_UFAILURE(NULL, "transcode", error);
768 RETVAL_FALSE;
769 }
770 } else {
771 RETVAL_FALSE;
772 }
773
774 if (src_cnv) {
775 ucnv_close(src_cnv);
776 }
777 if (dest_cnv) {
778 ucnv_close(dest_cnv);
779 }
780 }
781 /* }}} */
782
783 /* {{{ */
PHP_METHOD(UConverter,getErrorCode)784 PHP_METHOD(UConverter, getErrorCode) {
785 php_converter_object *objval = CONV_GET(ZEND_THIS);
786
787 if (zend_parse_parameters_none() == FAILURE) {
788 RETURN_THROWS();
789 }
790
791 RETURN_LONG(intl_error_get_code(&(objval->error)));
792 }
793 /* }}} */
794
795 /* {{{ */
PHP_METHOD(UConverter,getErrorMessage)796 PHP_METHOD(UConverter, getErrorMessage) {
797 php_converter_object *objval = CONV_GET(ZEND_THIS);
798
799 if (zend_parse_parameters_none() == FAILURE) {
800 RETURN_THROWS();
801 }
802
803 zend_string *message = intl_error_get_message(&(objval->error));
804 if (message) {
805 RETURN_STR(message);
806 } else {
807 RETURN_NULL();
808 }
809 }
810 /* }}} */
811
812 /* {{{ */
PHP_METHOD(UConverter,getAvailable)813 PHP_METHOD(UConverter, getAvailable) {
814 int32_t i,
815 count = ucnv_countAvailable();
816
817 if (zend_parse_parameters_none() == FAILURE) {
818 RETURN_THROWS();
819 }
820 intl_error_reset(NULL);
821
822 array_init(return_value);
823 for(i = 0; i < count; i++) {
824 const char *name = ucnv_getAvailableName(i);
825 add_next_index_string(return_value, name);
826 }
827 }
828 /* }}} */
829
830 /* {{{ */
PHP_METHOD(UConverter,getAliases)831 PHP_METHOD(UConverter, getAliases) {
832 char *name;
833 size_t name_len;
834 UErrorCode error = U_ZERO_ERROR;
835 uint16_t i, count;
836
837 if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
838 RETURN_THROWS();
839 }
840 intl_error_reset(NULL);
841
842 count = ucnv_countAliases(name, &error);
843 if (U_FAILURE(error)) {
844 THROW_UFAILURE(NULL, "ucnv_countAliases", error);
845 RETURN_FALSE;
846 }
847
848 array_init(return_value);
849 for(i = 0; i < count; i++) {
850 const char *alias;
851
852 error = U_ZERO_ERROR;
853 alias = ucnv_getAlias(name, i, &error);
854 if (U_FAILURE(error)) {
855 THROW_UFAILURE(NULL, "ucnv_getAlias", error);
856 zend_array_destroy(Z_ARR_P(return_value));
857 RETURN_NULL();
858 }
859 add_next_index_string(return_value, alias);
860 }
861 }
862 /* }}} */
863
864 /* {{{ */
PHP_METHOD(UConverter,getStandards)865 PHP_METHOD(UConverter, getStandards) {
866 uint16_t i, count;
867
868 if (zend_parse_parameters_none() == FAILURE) {
869 RETURN_THROWS();
870 }
871 intl_error_reset(NULL);
872
873 array_init(return_value);
874 count = ucnv_countStandards();
875 for(i = 0; i < count; i++) {
876 UErrorCode error = U_ZERO_ERROR;
877 const char *name = ucnv_getStandard(i, &error);
878 if (U_FAILURE(error)) {
879 THROW_UFAILURE(NULL, "ucnv_getStandard", error);
880 zend_array_destroy(Z_ARR_P(return_value));
881 RETURN_NULL();
882 }
883 add_next_index_string(return_value, name);
884 }
885 }
886 /* }}} */
887
888 /* {{{ Converter create/clone/destroy */
php_converter_free_object(zend_object * obj)889 static void php_converter_free_object(zend_object *obj) {
890 php_converter_object *objval = php_converter_fetch_object(obj);
891
892 if (objval->src) {
893 ucnv_close(objval->src);
894 }
895
896 if (objval->dest) {
897 ucnv_close(objval->dest);
898 }
899
900 intl_error_reset(&objval->error);
901 zend_object_std_dtor(obj);
902 }
903
php_converter_object_ctor(zend_class_entry * ce,php_converter_object ** pobjval)904 static zend_object *php_converter_object_ctor(zend_class_entry *ce, php_converter_object **pobjval) {
905 php_converter_object *objval;
906
907 objval = zend_object_alloc(sizeof(php_converter_object), ce);
908
909 zend_object_std_init(&objval->obj, ce);
910 object_properties_init(&objval->obj, ce);
911 intl_error_init(&(objval->error));
912
913 *pobjval = objval;
914
915 return &objval->obj;
916 }
917
php_converter_create_object(zend_class_entry * ce)918 static zend_object *php_converter_create_object(zend_class_entry *ce) {
919 php_converter_object *objval = NULL;
920 zend_object *retval = php_converter_object_ctor(ce, &objval);
921
922 object_properties_init(&(objval->obj), ce);
923
924 return retval;
925 }
926
php_converter_clone_object(zend_object * object)927 static zend_object *php_converter_clone_object(zend_object *object) {
928 php_converter_object *objval, *oldobj = php_converter_fetch_object(object);
929 zend_object *retval = php_converter_object_ctor(object->ce, &objval);
930 UErrorCode error = U_ZERO_ERROR;
931
932 intl_errors_reset(&oldobj->error);
933
934 #if U_ICU_VERSION_MAJOR_NUM > 70
935 objval->src = ucnv_clone(oldobj->src, &error);
936 #else
937 objval->src = ucnv_safeClone(oldobj->src, NULL, NULL, &error);
938 #endif
939 if (U_SUCCESS(error)) {
940 error = U_ZERO_ERROR;
941 #if U_ICU_VERSION_MAJOR_NUM > 70
942 objval->dest = ucnv_clone(oldobj->dest, &error);
943 #else
944 objval->dest = ucnv_safeClone(oldobj->dest, NULL, NULL, &error);
945 #endif
946 }
947 if (U_FAILURE(error)) {
948 zend_string *err_msg;
949 THROW_UFAILURE(oldobj, "ucnv_safeClone", error);
950
951 err_msg = intl_error_get_message(&oldobj->error);
952 zend_throw_exception(NULL, ZSTR_VAL(err_msg), 0);
953 zend_string_release_ex(err_msg, 0);
954
955 return retval;
956 }
957
958 /* Update contexts for converter error handlers */
959 php_converter_set_callbacks(objval, objval->src );
960 php_converter_set_callbacks(objval, objval->dest);
961
962 zend_objects_clone_members(&(objval->obj), &(oldobj->obj));
963
964 /* Newly cloned object deliberately does not inherit error state from original object */
965
966 return retval;
967 }
968 /* }}} */
969
970 /* {{{ php_converter_minit */
php_converter_minit(INIT_FUNC_ARGS)971 int php_converter_minit(INIT_FUNC_ARGS) {
972 php_converter_ce = register_class_UConverter();
973 php_converter_ce->create_object = php_converter_create_object;
974 php_converter_ce->default_object_handlers = &php_converter_object_handlers;
975 memcpy(&php_converter_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
976 php_converter_object_handlers.offset = XtOffsetOf(php_converter_object, obj);
977 php_converter_object_handlers.clone_obj = php_converter_clone_object;
978 php_converter_object_handlers.free_obj = php_converter_free_object;
979
980 return SUCCESS;
981 }
982 /* }}} */
983