xref: /php-src/ext/intl/idn/idn.c (revision 66c4ade0)
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