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