xref: /PHP-8.2/ext/gettext/gettext.c (revision 33967aef)
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: Alex Plotnick <alex@wgate.com>                               |
14    +----------------------------------------------------------------------+
15  */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "php.h"
22 
23 #ifdef HAVE_LIBINTL
24 
25 #include <stdio.h>
26 #include <locale.h>
27 #include "ext/standard/info.h"
28 #include "php_gettext.h"
29 #include "gettext_arginfo.h"
30 
31 #include <libintl.h>
32 
33 zend_module_entry php_gettext_module_entry = {
34 	STANDARD_MODULE_HEADER,
35 	"gettext",
36 	ext_functions,
37 	NULL,
38 	NULL,
39 	NULL,
40 	NULL,
41 	PHP_MINFO(php_gettext),
42 	PHP_GETTEXT_VERSION,
43 	STANDARD_MODULE_PROPERTIES
44 };
45 
46 #ifdef COMPILE_DL_GETTEXT
47 ZEND_GET_MODULE(php_gettext)
48 #endif
49 
50 #define PHP_GETTEXT_MAX_DOMAIN_LENGTH 1024
51 #define PHP_GETTEXT_MAX_MSGID_LENGTH 4096
52 
53 #define PHP_GETTEXT_DOMAIN_LENGTH_CHECK(_arg_num, domain_len) \
54 	if (UNEXPECTED(domain_len > PHP_GETTEXT_MAX_DOMAIN_LENGTH)) { \
55 		zend_argument_value_error(_arg_num, "is too long"); \
56 		RETURN_THROWS(); \
57 	}
58 
59 #define PHP_GETTEXT_LENGTH_CHECK(_arg_num, check_len) \
60 	if (UNEXPECTED(check_len > PHP_GETTEXT_MAX_MSGID_LENGTH)) { \
61 		zend_argument_value_error(_arg_num, "is too long"); \
62 		RETURN_THROWS(); \
63 	}
64 
PHP_MINFO_FUNCTION(php_gettext)65 PHP_MINFO_FUNCTION(php_gettext)
66 {
67 	php_info_print_table_start();
68 	php_info_print_table_row(2, "GetText Support", "enabled");
69 	php_info_print_table_end();
70 }
71 
72 /* {{{ Set the textdomain to "domain". Returns the current domain */
PHP_FUNCTION(textdomain)73 PHP_FUNCTION(textdomain)
74 {
75 	char *domain_name = NULL, *retval;
76 	zend_string *domain = NULL;
77 
78 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "S!", &domain) == FAILURE) {
79 		RETURN_THROWS();
80 	}
81 
82 	if (domain != NULL && ZSTR_LEN(domain) != 0 && !zend_string_equals_literal(domain, "0")) {
83 		PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, ZSTR_LEN(domain))
84 		domain_name = ZSTR_VAL(domain);
85 	}
86 
87 	retval = textdomain(domain_name);
88 
89 	RETURN_STRING(retval);
90 }
91 /* }}} */
92 
93 /* {{{ Return the translation of msgid for the current domain, or msgid unaltered if a translation does not exist */
PHP_FUNCTION(gettext)94 PHP_FUNCTION(gettext)
95 {
96 	char *msgstr;
97 	zend_string *msgid;
98 
99 	ZEND_PARSE_PARAMETERS_START(1, 1)
100 		Z_PARAM_STR(msgid)
101 	ZEND_PARSE_PARAMETERS_END();
102 
103 	PHP_GETTEXT_LENGTH_CHECK(1, ZSTR_LEN(msgid))
104 	msgstr = gettext(ZSTR_VAL(msgid));
105 
106 	if (msgstr != ZSTR_VAL(msgid)) {
107 		RETURN_STRING(msgstr);
108 	} else {
109 		RETURN_STR_COPY(msgid);
110 	}
111 }
112 /* }}} */
113 
114 /* {{{ Return the translation of msgid for domain_name, or msgid unaltered if a translation does not exist */
PHP_FUNCTION(dgettext)115 PHP_FUNCTION(dgettext)
116 {
117 	char *msgstr;
118 	zend_string *domain, *msgid;
119 
120 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &domain, &msgid) == FAILURE)	{
121 		RETURN_THROWS();
122 	}
123 
124 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, ZSTR_LEN(domain))
125 	PHP_GETTEXT_LENGTH_CHECK(2, ZSTR_LEN(msgid))
126 
127 	msgstr = dgettext(ZSTR_VAL(domain), ZSTR_VAL(msgid));
128 
129 	if (msgstr != ZSTR_VAL(msgid)) {
130 		RETURN_STRING(msgstr);
131 	} else {
132 		RETURN_STR_COPY(msgid);
133 	}
134 }
135 /* }}} */
136 
137 /* {{{ Return the translation of msgid for domain_name and category, or msgid unaltered if a translation does not exist */
PHP_FUNCTION(dcgettext)138 PHP_FUNCTION(dcgettext)
139 {
140 	char *msgstr;
141 	zend_string *domain, *msgid;
142 	zend_long category;
143 
144 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSl", &domain, &msgid, &category) == FAILURE) {
145 		RETURN_THROWS();
146 	}
147 
148 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, ZSTR_LEN(domain))
149 	PHP_GETTEXT_LENGTH_CHECK(2, ZSTR_LEN(msgid))
150 	if (category == LC_ALL) {
151 		RETURN_STR_COPY(msgid);
152 	}
153 
154 	msgstr = dcgettext(ZSTR_VAL(domain), ZSTR_VAL(msgid), category);
155 
156 	if (msgstr != ZSTR_VAL(msgid)) {
157 		RETURN_STRING(msgstr);
158 	} else {
159 		RETURN_STR_COPY(msgid);
160 	}
161 }
162 /* }}} */
163 
164 /* {{{ Bind to the text domain domain_name, looking for translations in dir. Returns the current domain */
PHP_FUNCTION(bindtextdomain)165 PHP_FUNCTION(bindtextdomain)
166 {
167 	char *domain;
168 	size_t domain_len;
169 	zend_string *dir = NULL;
170 	char *retval, dir_name[MAXPATHLEN];
171 
172 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS!", &domain, &domain_len, &dir) == FAILURE) {
173 		RETURN_THROWS();
174 	}
175 
176 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, domain_len)
177 
178 	if (domain[0] == '\0') {
179 		zend_argument_value_error(1, "cannot be empty");
180 		RETURN_THROWS();
181 	}
182 
183 	if (dir == NULL) {
184 		RETURN_STRING(bindtextdomain(domain, NULL));
185 	}
186 
187 	if (ZSTR_LEN(dir) != 0 && !zend_string_equals_literal(dir, "0")) {
188 		if (!VCWD_REALPATH(ZSTR_VAL(dir), dir_name)) {
189 			RETURN_FALSE;
190 		}
191 	} else if (!VCWD_GETCWD(dir_name, MAXPATHLEN)) {
192 		RETURN_FALSE;
193 	}
194 
195 	retval = bindtextdomain(domain, dir_name);
196 
197 	RETURN_STRING(retval);
198 }
199 /* }}} */
200 
201 #ifdef HAVE_NGETTEXT
202 /* {{{ Plural version of gettext() */
PHP_FUNCTION(ngettext)203 PHP_FUNCTION(ngettext)
204 {
205 	char *msgid1, *msgid2, *msgstr;
206 	size_t msgid1_len, msgid2_len;
207 	zend_long count;
208 
209 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &msgid1, &msgid1_len, &msgid2, &msgid2_len, &count) == FAILURE) {
210 		RETURN_THROWS();
211 	}
212 
213 	PHP_GETTEXT_LENGTH_CHECK(1, msgid1_len)
214 	PHP_GETTEXT_LENGTH_CHECK(2, msgid2_len)
215 
216 	msgstr = ngettext(msgid1, msgid2, count);
217 
218 	ZEND_ASSERT(msgstr);
219 	RETURN_STRING(msgstr);
220 }
221 /* }}} */
222 #endif
223 
224 #ifdef HAVE_DNGETTEXT
225 /* {{{ Plural version of dgettext() */
PHP_FUNCTION(dngettext)226 PHP_FUNCTION(dngettext)
227 {
228 	char *domain, *msgid1, *msgid2, *msgstr = NULL;
229 	size_t domain_len, msgid1_len, msgid2_len;
230 	zend_long count;
231 
232 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssl", &domain, &domain_len,
233 		&msgid1, &msgid1_len, &msgid2, &msgid2_len, &count) == FAILURE) {
234 		RETURN_THROWS();
235 	}
236 
237 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, domain_len)
238 	PHP_GETTEXT_LENGTH_CHECK(2, msgid1_len)
239 	PHP_GETTEXT_LENGTH_CHECK(3, msgid2_len)
240 
241 	msgstr = dngettext(domain, msgid1, msgid2, count);
242 
243 	ZEND_ASSERT(msgstr);
244 	RETURN_STRING(msgstr);
245 }
246 /* }}} */
247 #endif
248 
249 #ifdef HAVE_DCNGETTEXT
250 /* {{{ Plural version of dcgettext() */
PHP_FUNCTION(dcngettext)251 PHP_FUNCTION(dcngettext)
252 {
253 	char *domain, *msgid1, *msgid2, *msgstr = NULL;
254 	size_t domain_len, msgid1_len, msgid2_len;
255 	zend_long count, category;
256 
257 	RETVAL_FALSE;
258 
259 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssll", &domain, &domain_len,
260 		&msgid1, &msgid1_len, &msgid2, &msgid2_len, &count, &category) == FAILURE) {
261 		RETURN_THROWS();
262 	}
263 
264 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, domain_len)
265 	PHP_GETTEXT_LENGTH_CHECK(2, msgid1_len)
266 	PHP_GETTEXT_LENGTH_CHECK(3, msgid2_len)
267 	if (category == LC_ALL) {
268 		RETURN_STRING(msgid1);
269 	}
270 
271 	msgstr = dcngettext(domain, msgid1, msgid2, count, category);
272 
273 	ZEND_ASSERT(msgstr);
274 	RETURN_STRING(msgstr);
275 }
276 /* }}} */
277 #endif
278 
279 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
280 
281 /* {{{ Specify the character encoding in which the messages from the DOMAIN message catalog will be returned. */
PHP_FUNCTION(bind_textdomain_codeset)282 PHP_FUNCTION(bind_textdomain_codeset)
283 {
284 	char *domain, *codeset = NULL, *retval = NULL;
285 	size_t domain_len, codeset_len;
286 
287 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss!", &domain, &domain_len, &codeset, &codeset_len) == FAILURE) {
288 		RETURN_THROWS();
289 	}
290 
291 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(1, domain_len)
292 
293 	retval = bind_textdomain_codeset(domain, codeset);
294 
295 	if (!retval) {
296 		RETURN_FALSE;
297 	}
298 	RETURN_STRING(retval);
299 }
300 /* }}} */
301 #endif
302 
303 
304 #endif /* HAVE_LIBINTL */
305