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 #include "ext/standard/php_string.h"
28
29 #include "idn.h"
30 #include "intl_error.h"
31 #include "intl_convert.h"
32 /* }}} */
33
34 enum {
35 INTL_IDN_TO_ASCII = 0,
36 INTL_IDN_TO_UTF8
37 };
38
39 /* 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)40 static int php_intl_idn_check_status(UErrorCode err, const char *msg)
41 {
42 intl_error_set_code(NULL, err);
43 if (U_FAILURE(err)) {
44 char *buff;
45 spprintf(&buff, 0, "%s: %s",
46 get_active_function_name(),
47 msg);
48 intl_error_set_custom_msg(NULL, buff, 1);
49 efree(buff);
50 return FAILURE;
51 }
52
53 return SUCCESS;
54 }
55
php_intl_bad_args(const char * msg)56 static inline void php_intl_bad_args(const char *msg)
57 {
58 php_intl_idn_check_status(U_ILLEGAL_ARGUMENT_ERROR, msg);
59 }
60
php_intl_idn_to_46(INTERNAL_FUNCTION_PARAMETERS,const zend_string * domain,uint32_t option,int mode,zval * idna_info)61 static void php_intl_idn_to_46(INTERNAL_FUNCTION_PARAMETERS,
62 const zend_string *domain, uint32_t option, int mode, zval *idna_info)
63 {
64 UErrorCode status = U_ZERO_ERROR;
65 UIDNA *uts46;
66 int32_t len;
67 zend_string *buffer;
68 UIDNAInfo info = UIDNA_INFO_INITIALIZER;
69
70 uts46 = uidna_openUTS46(option, &status);
71 if (php_intl_idn_check_status(status, "failed to open UIDNA instance") == FAILURE) {
72 RETURN_FALSE;
73 }
74
75 if (mode == INTL_IDN_TO_ASCII) {
76 const int32_t buffer_capac = 255;
77 buffer = zend_string_alloc(buffer_capac, 0);
78 len = uidna_nameToASCII_UTF8(uts46, ZSTR_VAL(domain), ZSTR_LEN(domain),
79 ZSTR_VAL(buffer), buffer_capac, &info, &status);
80 if (len >= buffer_capac || php_intl_idn_check_status(status, "failed to convert name") == FAILURE) {
81 uidna_close(uts46);
82 zend_string_efree(buffer);
83 RETURN_FALSE;
84 }
85 } else {
86 const int32_t buffer_capac = 252*4;
87 buffer = zend_string_alloc(buffer_capac, 0);
88 len = uidna_nameToUnicodeUTF8(uts46, ZSTR_VAL(domain), ZSTR_LEN(domain),
89 ZSTR_VAL(buffer), buffer_capac, &info, &status);
90 if (len >= buffer_capac || php_intl_idn_check_status(status, "failed to convert name") == FAILURE) {
91 uidna_close(uts46);
92 zend_string_efree(buffer);
93 RETURN_FALSE;
94 }
95 }
96
97 ZSTR_VAL(buffer)[len] = '\0';
98 ZSTR_LEN(buffer) = len;
99
100 if (info.errors == 0) {
101 RETVAL_STR_COPY(buffer);
102 } else {
103 RETVAL_FALSE;
104 }
105
106 if (idna_info) {
107 add_assoc_str_ex(idna_info, "result", sizeof("result")-1, zend_string_copy(buffer));
108 add_assoc_bool_ex(idna_info, "isTransitionalDifferent",
109 sizeof("isTransitionalDifferent")-1, info.isTransitionalDifferent);
110 add_assoc_long_ex(idna_info, "errors", sizeof("errors")-1, (zend_long)info.errors);
111 }
112
113 zend_string_release(buffer);
114 uidna_close(uts46);
115 }
116
php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS,int mode)117 static void php_intl_idn_handoff(INTERNAL_FUNCTION_PARAMETERS, int mode)
118 {
119 zend_string *domain;
120 zend_long option = UIDNA_DEFAULT,
121 variant = INTL_IDN_VARIANT_UTS46;
122 zval *idna_info = NULL;
123
124 intl_error_reset(NULL);
125
126 if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|llz",
127 &domain, &option, &variant, &idna_info) == FAILURE) {
128 RETURN_THROWS();
129 }
130
131 if (variant != INTL_IDN_VARIANT_UTS46) {
132 php_intl_bad_args("invalid variant, must be INTL_IDNA_VARIANT_UTS46");
133 RETURN_FALSE;
134 }
135
136 if (ZSTR_LEN(domain) < 1) {
137 php_intl_bad_args("empty domain name");
138 RETURN_FALSE;
139 }
140 if (ZSTR_LEN(domain) > INT32_MAX - 1) {
141 php_intl_bad_args("domain name too large");
142 RETURN_FALSE;
143 }
144 /* don't check options; it wasn't checked before */
145
146 if (idna_info != NULL) {
147 idna_info = zend_try_array_init(idna_info);
148 if (!idna_info) {
149 RETURN_THROWS();
150 }
151 }
152
153 php_intl_idn_to_46(INTERNAL_FUNCTION_PARAM_PASSTHRU, domain, (uint32_t)option, mode, idna_info);
154 }
155
156 /* {{{ Converts an Unicode domain to ASCII representation, as defined in the IDNA RFC */
PHP_FUNCTION(idn_to_ascii)157 PHP_FUNCTION(idn_to_ascii)
158 {
159 php_intl_idn_handoff(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTL_IDN_TO_ASCII);
160 }
161 /* }}} */
162
163
164 /* {{{ Converts an ASCII representation of the domain to Unicode (UTF-8), as defined in the IDNA RFC */
PHP_FUNCTION(idn_to_utf8)165 PHP_FUNCTION(idn_to_utf8)
166 {
167 php_intl_idn_handoff(INTERNAL_FUNCTION_PARAM_PASSTHRU, INTL_IDN_TO_UTF8);
168 }
169 /* }}} */
170