1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) The PHP Group                                          |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 3.01 of the PHP license,      |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available through the world-wide-web at the following url:           |
10    | http://www.php.net/license/3_01.txt                                  |
11    | If you did not receive a copy of the PHP license and are unable to   |
12    | obtain it through the world-wide-web, please send a note to          |
13    | license@php.net so we can mail you a copy immediately.               |
14    +----------------------------------------------------------------------+
15    | Author: Alex Plotnick <alex@wgate.com>                               |
16    +----------------------------------------------------------------------+
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include "php.h"
24 
25 #if HAVE_LIBINTL
26 
27 #include <stdio.h>
28 #include "ext/standard/info.h"
29 #include "php_gettext.h"
30 
31 /* {{{ arginfo */
32 ZEND_BEGIN_ARG_INFO(arginfo_textdomain, 0)
33 	ZEND_ARG_INFO(0, domain)
34 ZEND_END_ARG_INFO()
35 
36 ZEND_BEGIN_ARG_INFO(arginfo_gettext, 0)
37 	ZEND_ARG_INFO(0, msgid)
38 ZEND_END_ARG_INFO()
39 
40 ZEND_BEGIN_ARG_INFO(arginfo_dgettext, 0)
41 	ZEND_ARG_INFO(0, domain_name)
42 	ZEND_ARG_INFO(0, msgid)
43 ZEND_END_ARG_INFO()
44 
45 ZEND_BEGIN_ARG_INFO(arginfo_dcgettext, 0)
46 	ZEND_ARG_INFO(0, domain_name)
47 	ZEND_ARG_INFO(0, msgid)
48 	ZEND_ARG_INFO(0, category)
49 ZEND_END_ARG_INFO()
50 
51 ZEND_BEGIN_ARG_INFO(arginfo_bindtextdomain, 0)
52 	ZEND_ARG_INFO(0, domain_name)
53 	ZEND_ARG_INFO(0, dir)
54 ZEND_END_ARG_INFO()
55 
56 #if HAVE_NGETTEXT
57 ZEND_BEGIN_ARG_INFO(arginfo_ngettext, 0)
58 	ZEND_ARG_INFO(0, msgid1)
59 	ZEND_ARG_INFO(0, msgid2)
60 	ZEND_ARG_INFO(0, count)
61 ZEND_END_ARG_INFO()
62 #endif
63 
64 #if HAVE_DNGETTEXT
65 ZEND_BEGIN_ARG_INFO(arginfo_dngettext, 0)
66 	ZEND_ARG_INFO(0, domain)
67 	ZEND_ARG_INFO(0, msgid1)
68 	ZEND_ARG_INFO(0, msgid2)
69 	ZEND_ARG_INFO(0, count)
70 ZEND_END_ARG_INFO()
71 #endif
72 
73 #if HAVE_DCNGETTEXT
74 ZEND_BEGIN_ARG_INFO(arginfo_dcngettext, 0)
75 	ZEND_ARG_INFO(0, domain)
76 	ZEND_ARG_INFO(0, msgid1)
77 	ZEND_ARG_INFO(0, msgid2)
78 	ZEND_ARG_INFO(0, count)
79 	ZEND_ARG_INFO(0, category)
80 ZEND_END_ARG_INFO()
81 #endif
82 
83 #if HAVE_BIND_TEXTDOMAIN_CODESET
84 ZEND_BEGIN_ARG_INFO(arginfo_bind_textdomain_codeset, 0)
85 	ZEND_ARG_INFO(0, domain)
86 	ZEND_ARG_INFO(0, codeset)
87 ZEND_END_ARG_INFO()
88 #endif
89 /* }}} */
90 
91 /* {{{ php_gettext_functions[]
92  */
93 static const zend_function_entry php_gettext_functions[] = {
94 	PHP_NAMED_FE(textdomain,		zif_textdomain,		arginfo_textdomain)
95 	PHP_NAMED_FE(gettext,			zif_gettext,		arginfo_gettext)
96 	/* Alias for gettext() */
97 	PHP_NAMED_FE(_,					zif_gettext,		arginfo_gettext)
98 	PHP_NAMED_FE(dgettext,			zif_dgettext,		arginfo_dgettext)
99 	PHP_NAMED_FE(dcgettext,			zif_dcgettext,		arginfo_dcgettext)
100 	PHP_NAMED_FE(bindtextdomain,	zif_bindtextdomain,	arginfo_bindtextdomain)
101 #if HAVE_NGETTEXT
102 	PHP_NAMED_FE(ngettext,			zif_ngettext,		arginfo_ngettext)
103 #endif
104 #if HAVE_DNGETTEXT
105 	PHP_NAMED_FE(dngettext,			zif_dngettext,		arginfo_dngettext)
106 #endif
107 #if HAVE_DCNGETTEXT
108 	PHP_NAMED_FE(dcngettext,		zif_dcngettext,		arginfo_dcngettext)
109 #endif
110 #if HAVE_BIND_TEXTDOMAIN_CODESET
111 	PHP_NAMED_FE(bind_textdomain_codeset,	zif_bind_textdomain_codeset,	arginfo_bind_textdomain_codeset)
112 #endif
113     PHP_FE_END
114 };
115 /* }}} */
116 
117 #include <libintl.h>
118 
119 zend_module_entry php_gettext_module_entry = {
120 	STANDARD_MODULE_HEADER,
121 	"gettext",
122 	php_gettext_functions,
123 	NULL,
124 	NULL,
125 	NULL,
126 	NULL,
127 	PHP_MINFO(php_gettext),
128 	PHP_GETTEXT_VERSION,
129 	STANDARD_MODULE_PROPERTIES
130 };
131 
132 #ifdef COMPILE_DL_GETTEXT
ZEND_GET_MODULEnull133 ZEND_GET_MODULE(php_gettext)
134 #endif
135 
136 #define PHP_GETTEXT_MAX_DOMAIN_LENGTH 1024
137 #define PHP_GETTEXT_MAX_MSGID_LENGTH 4096
138 
139 #define PHP_GETTEXT_DOMAIN_LENGTH_CHECK(domain_len) \
140 	if (UNEXPECTED(domain_len > PHP_GETTEXT_MAX_DOMAIN_LENGTH)) { \
141 		php_error_docref(NULL, E_WARNING, "domain passed too long"); \
142 		RETURN_FALSE; \
143 	}
144 
145 #define PHP_GETTEXT_LENGTH_CHECK(check_name, check_len) \
146 	if (UNEXPECTED(check_len > PHP_GETTEXT_MAX_MSGID_LENGTH)) { \
147 		php_error_docref(NULL, E_WARNING, "%s passed too long", check_name); \
148 		RETURN_FALSE; \
149 	}
150 
151 PHP_MINFO_FUNCTION(php_gettext)
152 {
153 	php_info_print_table_start();
154 	php_info_print_table_row(2, "GetText Support", "enabled");
155 	php_info_print_table_end();
156 }
157 
158 /* {{{ proto string textdomain(string domain)
159    Set the textdomain to "domain". Returns the current domain */
PHP_NAMED_FUNCTIONnull160 PHP_NAMED_FUNCTION(zif_textdomain)
161 {
162 	char *domain = NULL, *domain_name, *retval;
163 	size_t domain_len = 0;
164 
165 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s!", &domain, &domain_len) == FAILURE) {
166 		return;
167 	}
168 
169 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(domain_len)
170 
171 	if (domain != NULL && strcmp(domain, "") && strcmp(domain, "0")) {
172 		domain_name = domain;
173 	} else {
174 		domain_name = NULL;
175 	}
176 
177 	retval = textdomain(domain_name);
178 
179 	RETURN_STRING(retval);
180 }
181 /* }}} */
182 
183 /* {{{ proto string gettext(string msgid)
184    Return the translation of msgid for the current domain, or msgid unaltered if a translation does not exist */
PHP_NAMED_FUNCTIONnull185 PHP_NAMED_FUNCTION(zif_gettext)
186 {
187 	char *msgstr;
188 	zend_string *msgid;
189 
190 	ZEND_PARSE_PARAMETERS_START(1, 1)
191 		Z_PARAM_STR(msgid)
192 	ZEND_PARSE_PARAMETERS_END();
193 
194 	PHP_GETTEXT_LENGTH_CHECK("msgid", ZSTR_LEN(msgid))
195 	msgstr = gettext(ZSTR_VAL(msgid));
196 
197 	if (msgstr != ZSTR_VAL(msgid)) {
198 		RETURN_STRING(msgstr);
199 	} else {
200 		RETURN_STR_COPY(msgid);
201 	}
202 }
203 /* }}} */
204 
205 /* {{{ proto string dgettext(string domain_name, string msgid)
206    Return the translation of msgid for domain_name, or msgid unaltered if a translation does not exist */
PHP_NAMED_FUNCTIONnull207 PHP_NAMED_FUNCTION(zif_dgettext)
208 {
209 	char *msgstr;
210 	zend_string *domain, *msgid;
211 
212 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS", &domain, &msgid) == FAILURE)	{
213 		return;
214 	}
215 
216 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(ZSTR_LEN(domain))
217 	PHP_GETTEXT_LENGTH_CHECK("msgid", ZSTR_LEN(msgid))
218 
219 	msgstr = dgettext(ZSTR_VAL(domain), ZSTR_VAL(msgid));
220 
221 	if (msgstr != ZSTR_VAL(msgid)) {
222 		RETURN_STRING(msgstr);
223 	} else {
224 		RETURN_STR_COPY(msgid);
225 	}
226 }
227 /* }}} */
228 
229 /* {{{ proto string dcgettext(string domain_name, string msgid, int category)
230    Return the translation of msgid for domain_name and category, or msgid unaltered if a translation does not exist */
PHP_NAMED_FUNCTIONnull231 PHP_NAMED_FUNCTION(zif_dcgettext)
232 {
233 	char *msgstr;
234 	zend_string *domain, *msgid;
235 	zend_long category;
236 
237 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "SSl", &domain, &msgid, &category) == FAILURE) {
238 		return;
239 	}
240 
241 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(ZSTR_LEN(domain))
242 	PHP_GETTEXT_LENGTH_CHECK("msgid", ZSTR_LEN(msgid))
243 
244 	msgstr = dcgettext(ZSTR_VAL(domain), ZSTR_VAL(msgid), category);
245 
246 	if (msgstr != ZSTR_VAL(msgid)) {
247 		RETURN_STRING(msgstr);
248 	} else {
249 		RETURN_STR_COPY(msgid);
250 	}
251 }
252 /* }}} */
253 
254 /* {{{ proto string bindtextdomain(string domain_name, string dir)
255    Bind to the text domain domain_name, looking for translations in dir. Returns the current domain */
PHP_NAMED_FUNCTIONnull256 PHP_NAMED_FUNCTION(zif_bindtextdomain)
257 {
258 	char *domain, *dir;
259 	size_t domain_len, dir_len;
260 	char *retval, dir_name[MAXPATHLEN];
261 
262 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &domain, &domain_len, &dir, &dir_len) == FAILURE) {
263 		return;
264 	}
265 
266 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(domain_len)
267 
268 	if (domain[0] == '\0') {
269 		php_error(E_WARNING, "The first parameter of bindtextdomain must not be empty");
270 		RETURN_FALSE;
271 	}
272 
273 	if (dir[0] != '\0' && strcmp(dir, "0")) {
274 		if (!VCWD_REALPATH(dir, dir_name)) {
275 			RETURN_FALSE;
276 		}
277 	} else if (!VCWD_GETCWD(dir_name, MAXPATHLEN)) {
278 		RETURN_FALSE;
279 	}
280 
281 	retval = bindtextdomain(domain, dir_name);
282 
283 	RETURN_STRING(retval);
284 }
285 /* }}} */
286 
287 #if HAVE_NGETTEXT
288 /* {{{ proto string ngettext(string MSGID1, string MSGID2, int N)
289    Plural version of gettext() */
PHP_NAMED_FUNCTIONnull290 PHP_NAMED_FUNCTION(zif_ngettext)
291 {
292 	char *msgid1, *msgid2, *msgstr;
293 	size_t msgid1_len, msgid2_len;
294 	zend_long count;
295 
296 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ssl", &msgid1, &msgid1_len, &msgid2, &msgid2_len, &count) == FAILURE) {
297 		return;
298 	}
299 
300 	PHP_GETTEXT_LENGTH_CHECK("msgid1", msgid1_len)
301 	PHP_GETTEXT_LENGTH_CHECK("msgid2", msgid2_len)
302 
303 	msgstr = ngettext(msgid1, msgid2, count);
304 
305 	ZEND_ASSERT(msgstr);
306 	RETURN_STRING(msgstr);
307 }
308 /* }}} */
309 #endif
310 
311 #if HAVE_DNGETTEXT
312 /* {{{ proto string dngettext(string domain, string msgid1, string msgid2, int count)
313    Plural version of dgettext() */
PHP_NAMED_FUNCTIONnull314 PHP_NAMED_FUNCTION(zif_dngettext)
315 {
316 	char *domain, *msgid1, *msgid2, *msgstr = NULL;
317 	size_t domain_len, msgid1_len, msgid2_len;
318 	zend_long count;
319 
320 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssl", &domain, &domain_len,
321 		&msgid1, &msgid1_len, &msgid2, &msgid2_len, &count) == FAILURE) {
322 		return;
323 	}
324 
325 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(domain_len)
326 	PHP_GETTEXT_LENGTH_CHECK("msgid1", msgid1_len)
327 	PHP_GETTEXT_LENGTH_CHECK("msgid2", msgid2_len)
328 
329 	msgstr = dngettext(domain, msgid1, msgid2, count);
330 
331 	ZEND_ASSERT(msgstr);
332 	RETURN_STRING(msgstr);
333 }
334 /* }}} */
335 #endif
336 
337 #if HAVE_DCNGETTEXT
338 /* {{{ proto string dcngettext(string domain, string msgid1, string msgid2, int n, int category)
339    Plural version of dcgettext() */
PHP_NAMED_FUNCTIONnull340 PHP_NAMED_FUNCTION(zif_dcngettext)
341 {
342 	char *domain, *msgid1, *msgid2, *msgstr = NULL;
343 	size_t domain_len, msgid1_len, msgid2_len;
344 	zend_long count, category;
345 
346 	RETVAL_FALSE;
347 
348 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sssll", &domain, &domain_len,
349 		&msgid1, &msgid1_len, &msgid2, &msgid2_len, &count, &category) == FAILURE) {
350 		return;
351 	}
352 
353 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(domain_len)
354 	PHP_GETTEXT_LENGTH_CHECK("msgid1", msgid1_len)
355 	PHP_GETTEXT_LENGTH_CHECK("msgid2", msgid2_len)
356 
357 	msgstr = dcngettext(domain, msgid1, msgid2, count, category);
358 
359 	ZEND_ASSERT(msgstr);
360 	RETURN_STRING(msgstr);
361 }
362 /* }}} */
363 #endif
364 
365 #if HAVE_BIND_TEXTDOMAIN_CODESET
366 
367 /* {{{ proto string bind_textdomain_codeset(string domain, string codeset)
368    Specify the character encoding in which the messages from the DOMAIN message catalog will be returned. */
PHP_NAMED_FUNCTIONnull369 PHP_NAMED_FUNCTION(zif_bind_textdomain_codeset)
370 {
371 	char *domain, *codeset, *retval = NULL;
372 	size_t domain_len, codeset_len;
373 
374 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &domain, &domain_len, &codeset, &codeset_len) == FAILURE) {
375 		return;
376 	}
377 
378 	PHP_GETTEXT_DOMAIN_LENGTH_CHECK(domain_len)
379 
380 	retval = bind_textdomain_codeset(domain, codeset);
381 
382 	if (!retval) {
383 		RETURN_FALSE;
384 	}
385 	RETURN_STRING(retval);
386 }
387 /* }}} */
388 #endif
389 
390 
391 #endif /* HAVE_LIBINTL */
392