1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Pierre A. Joye <pierre@php.net> |
14 | Gustavo Lopes <cataphract@php.net> |
15 +----------------------------------------------------------------------+
16 */
17
18 /* {{{ includes */
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22
23 #include <php.h>
24
25 #include <unicode/uidna.h>
26 #include <unicode/ustring.h>
27
28 #include "idn.h"
29 #include "intl_error.h"
30 /* }}} */
31
32 enum {
33 INTL_IDN_TO_ASCII = 0,
34 INTL_IDN_TO_UTF8
35 };
36
37 /* like INTL_CHECK_STATUS, but as a function and varying the name of the func */
php_intl_idn_check_status(UErrorCode err,const char * msg)38 static zend_result php_intl_idn_check_status(UErrorCode err, const char *msg)
39 {
40 intl_error_set_code(NULL, err);
41 if (U_FAILURE(err)) {
42 char *buff;
43 spprintf(&buff, 0, "%s: %s",
44 get_active_function_name(),
45 msg);
46 intl_error_set_custom_msg(NULL, buff, 1);
47 efree(buff);
48 return FAILURE;
49 }
50
51 return SUCCESS;
52 }
53
php_intl_idn_to_46(INTERNAL_FUNCTION_PARAMETERS,const zend_string * domain,uint32_t option,int mode,zval * idna_info)54 static void php_intl_idn_to_46(INTERNAL_FUNCTION_PARAMETERS,
55 const zend_string *domain, uint32_t option, int mode, zval *idna_info)
56 {
57 UErrorCode status = U_ZERO_ERROR;
58 UIDNA *uts46;
59 int32_t len;
60 zend_string *buffer;
61 UIDNAInfo info = UIDNA_INFO_INITIALIZER;
62
63 uts46 = uidna_openUTS46(option, &status);
64 if (php_intl_idn_check_status(status, "failed to open UIDNA instance") == FAILURE) {
65 RETURN_FALSE;
66 }
67
68 if (mode == INTL_IDN_TO_ASCII) {
69 const int32_t buffer_capac = 255;
70 buffer = zend_string_alloc(buffer_capac, 0);
71 len = uidna_nameToASCII_UTF8(uts46, ZSTR_VAL(domain), ZSTR_LEN(domain),
72 ZSTR_VAL(buffer), buffer_capac, &info, &status);
73 if (len >= buffer_capac || php_intl_idn_check_status(status, "failed to convert name") == FAILURE) {
74 uidna_close(uts46);
75 zend_string_efree(buffer);
76 RETURN_FALSE;
77 }
78 } else {
79 const int32_t buffer_capac = 252*4;
80 buffer = zend_string_alloc(buffer_capac, 0);
81 len = uidna_nameToUnicodeUTF8(uts46, ZSTR_VAL(domain), ZSTR_LEN(domain),
82 ZSTR_VAL(buffer), buffer_capac, &info, &status);
83 if (len >= buffer_capac || php_intl_idn_check_status(status, "failed to convert name") == FAILURE) {
84 uidna_close(uts46);
85 zend_string_efree(buffer);
86 RETURN_FALSE;
87 }
88 }
89
90 ZSTR_VAL(buffer)[len] = '\0';
91 ZSTR_LEN(buffer) = len;
92
93 if (info.errors == 0) {
94 RETVAL_STR_COPY(buffer);
95 } else {
96 RETVAL_FALSE;
97 }
98
99 if (idna_info) {
100 add_assoc_str_ex(idna_info, "result", sizeof("result")-1, zend_string_copy(buffer));
101 add_assoc_bool_ex(idna_info, "isTransitionalDifferent",
102 sizeof("isTransitionalDifferent")-1, info.isTransitionalDifferent);
103 add_assoc_long_ex(idna_info, "errors", sizeof("errors")-1, (zend_long)info.errors);
104 }
105
106 zend_string_release(buffer);
107 uidna_close(uts46);
108 }
109
php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS,int mode)110 static void php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS, int mode)
111 {
112 zend_string *domain;
113 zend_long option = UIDNA_DEFAULT,
114 variant = INTL_IDN_VARIANT_UTS46;
115 zval *idna_info = NULL;
116
117 intl_error_reset(NULL);
118
119 ZEND_PARSE_PARAMETERS_START(1, 4)
120 Z_PARAM_STR(domain)
121 Z_PARAM_OPTIONAL
122 Z_PARAM_LONG(option)
123 Z_PARAM_LONG(variant)
124 Z_PARAM_ZVAL(idna_info)
125 ZEND_PARSE_PARAMETERS_END();
126
127 if (ZSTR_LEN(domain) == 0) {
128 zend_argument_must_not_be_empty_error(1);
129 RETURN_THROWS();
130 }
131 if (ZSTR_LEN(domain) > INT32_MAX - 1) {
132 zend_argument_value_error(1, "must be less than " PRId32 " bytes", INT32_MAX);
133 RETURN_THROWS();
134 }
135 if (variant != INTL_IDN_VARIANT_UTS46) {
136 zend_argument_value_error(2, "must be INTL_IDNA_VARIANT_UTS46");
137 RETURN_THROWS();
138 }
139 /* don't check options; it wasn't checked before */
140
141 if (idna_info != NULL) {
142 idna_info = zend_try_array_init(idna_info);
143 if (!idna_info) {
144 RETURN_THROWS();
145 }
146 }
147
148 php_intl_idn_to_46(INTERNAL_FUNCTION_PARAM_PASSTHRU, domain, (uint32_t)option, mode, idna_info);
149 }
150
151 /* {{{ Converts an Unicode domain to ASCII representation, as defined in the IDNA RFC */
PHP_FUNCTION(idn_to_ascii)152 PHP_FUNCTION(idn_to_ascii)
153 {
154 php_intl_idn_handoff(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTL_IDN_TO_ASCII);
155 }
156 /* }}} */
157
158
159 /* {{{ Converts an ASCII representation of the domain to Unicode (UTF-8), as defined in the IDNA RFC */
PHP_FUNCTION(idn_to_utf8)160 PHP_FUNCTION(idn_to_utf8)
161 {
162 php_intl_idn_handoff(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTL_IDN_TO_UTF8);
163 }
164 /* }}} */
165