1/*-
2 * Copyright (C) 2009 Gabor Kovesdan <gabor@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28__FBSDID("$FreeBSD$");
29
30#include <sys/endian.h>
31#include <sys/types.h>
32
33#include <err.h>
34#include <errno.h>
35#include <iconv.h>
36#include <stdbool.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40
41static bool uc_hook = false;
42static bool wc_hook = false;
43static bool mb_uc_fb = false;
44
45void	 unicode_hook(unsigned int mbr, void *data);
46void	 wchar_hook(wchar_t wc, void *data);
47
48void    mb_to_uc_fb(const char *, size_t,
49            void (*write_replacement) (const unsigned int *, size_t, void *),
50            void *, void *);
51
52static int
53ctl_get_translit1(void)
54{
55	iconv_t cd;
56	int arg, ret;
57
58	cd = iconv_open("ASCII//TRANSLIT", "UTF-8");
59	if (cd == (iconv_t)-1)
60		return (-1);
61	if (iconvctl(cd, ICONV_GET_TRANSLITERATE, &arg) == 0)
62		ret = (arg == 1) ? 0 : -1;
63	else
64		ret = -1;
65	if (iconv_close(cd) == -1)
66		return (-1);
67	return (ret);
68}
69
70static int
71ctl_get_translit2(void)
72{
73	iconv_t cd;
74	int arg, ret;
75
76	cd = iconv_open("ASCII", "UTF-8");
77	if (cd == (iconv_t)-1)
78		return (-1);
79	if (iconvctl(cd, ICONV_GET_TRANSLITERATE, &arg) == 0)
80		ret = (arg == 0) ? 0 : -1;
81	else
82		ret = -1;
83	if (iconv_close(cd) == -1)
84		return (-1);
85	return (ret);
86}
87
88static int
89ctl_set_translit1(void)
90{
91	iconv_t cd;
92	int arg = 1, ret;
93
94	cd = iconv_open("ASCII", "UTF-8");
95	if (cd == (iconv_t)-1)
96		return (-1);
97	ret = iconvctl(cd, ICONV_SET_TRANSLITERATE, &arg) == 0 ? 0 : -1;
98	if (iconv_close(cd) == -1)
99		return (-1);
100	return (ret);
101}
102
103static int
104ctl_set_translit2(void)
105{
106	iconv_t cd;
107	int arg = 0, ret;
108
109	cd = iconv_open("ASCII//TRANSLIT", "UTF-8");
110	if (cd == (iconv_t)-1)
111		return (-1);
112	ret = iconvctl(cd, ICONV_SET_TRANSLITERATE, &arg) == 0 ? 0 : -1;
113	if (iconv_close(cd) == -1)
114		return (-1);
115	return (ret);
116}
117
118static int
119ctl_get_discard_ilseq1(void)
120{
121	iconv_t cd;
122        int arg, ret;
123
124	cd = iconv_open("ASCII", "UTF-8");
125	if (cd == (iconv_t)-1)
126		return (-1);
127	if (iconvctl(cd, ICONV_GET_DISCARD_ILSEQ, &arg) == 0)
128		ret = arg == 0 ? 0 : -1;
129	else
130		ret = -1;
131	if (iconv_close(cd) == -1)
132		return (-1);
133	return (ret);
134}
135
136static int
137ctl_get_discard_ilseq2(void)
138{
139	iconv_t cd;
140	int arg, ret;
141
142	cd = iconv_open("ASCII//IGNORE", "UTF-8");
143	if (cd == (iconv_t)-1)
144		return (-1);
145	if (iconvctl(cd, ICONV_GET_DISCARD_ILSEQ, &arg) == 0)
146		ret = arg == 1 ? 0 : -1;
147	else
148		ret = -1;
149	if (iconv_close(cd) == -1)
150		return (-1);
151	return (ret);
152}
153
154static int
155ctl_set_discard_ilseq1(void)
156{
157	iconv_t cd;
158	int arg = 1, ret;
159
160	cd = iconv_open("ASCII", "UTF-8");
161	if (cd == (iconv_t)-1)
162		return (-1);
163	ret = iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &arg) == 0 ? 0 : -1;
164	if (iconv_close(cd) == -1)
165		return (-1);
166	return (ret);
167}
168
169static int
170ctl_set_discard_ilseq2(void)
171{
172	iconv_t cd;
173        int arg = 0, ret;
174
175	cd = iconv_open("ASCII//IGNORE", "UTF-8");
176	if (cd == (iconv_t)-1)
177	return (-1);
178	ret = iconvctl(cd, ICONV_SET_DISCARD_ILSEQ, &arg) == 0 ? 0 : -1;
179	if (iconv_close(cd) == -1)
180		return (-1);
181	return (ret);
182}
183
184static int
185ctl_trivialp1(void)
186{
187	iconv_t cd;
188        int arg, ret;
189
190	cd = iconv_open("latin2", "latin2");
191	if (cd == (iconv_t)-1)
192		return (-1);
193	if (iconvctl(cd, ICONV_TRIVIALP, &arg) == 0) {
194		ret = (arg == 1) ? 0 : -1;
195        } else
196                ret = -1;
197	if (iconv_close(cd) == -1)
198		return (-1);
199	return (ret);
200}
201
202static int
203ctl_trivialp2(void)
204{
205	iconv_t cd;
206	int arg, ret;
207
208	cd = iconv_open("ASCII", "KOI8-R");
209	if (cd == (iconv_t)-1)
210		return (-1);
211	if (iconvctl(cd, ICONV_TRIVIALP, &arg) == 0) {
212		ret = (arg == 0) ? 0 : -1;
213	} else
214		ret = -1;
215	if (iconv_close(cd) == -1)
216		return (-1);
217	return (ret);
218}
219
220void
221unicode_hook(unsigned int mbr, void *data)
222{
223
224#ifdef VERBOSE
225	printf("Unicode hook: %u\n", mbr);
226#endif
227	uc_hook = true;
228}
229
230void
231wchar_hook(wchar_t wc, void *data)
232{
233
234#ifdef VERBOSE
235	printf("Wchar hook: %ull\n", wc);
236#endif
237	wc_hook = true;
238}
239
240static int
241ctl_uc_hook(void)
242{
243	struct iconv_hooks hooks;
244	iconv_t cd;
245	size_t inbytesleft = 15, outbytesleft = 40;
246	const char **inptr;
247	const char *s = "Hello World!";
248	char **outptr;
249	char *outbuf;
250
251	inptr = &s;
252	hooks.uc_hook = unicode_hook;
253	hooks.wc_hook = NULL;
254
255	outbuf = malloc(40);
256	outptr = &outbuf;
257
258	cd = iconv_open("UTF-8", "ASCII");
259	if (cd == (iconv_t)-1)
260		return (-1);
261	if (iconvctl(cd, ICONV_SET_HOOKS, (void *)&hooks) != 0)
262		return (-1);
263	if (iconv(cd, inptr, &inbytesleft, outptr, &outbytesleft) == (size_t)-1)
264		return (-1);
265	if (iconv_close(cd) == -1)
266		return (-1);
267	return (uc_hook ? 0 : 1);
268}
269
270static int
271ctl_wc_hook(void)
272{
273	struct iconv_hooks hooks;
274	iconv_t cd;
275	size_t inbytesleft, outbytesleft = 40;
276	const char **inptr;
277	const char *s = "Hello World!";
278	char **outptr;
279	char *outbuf;
280
281	inptr = &s;
282	hooks.wc_hook = wchar_hook;
283	hooks.uc_hook = NULL;
284
285	outbuf = malloc(40);
286	outptr = &outbuf;
287	inbytesleft = sizeof(s);
288
289	cd = iconv_open("SHIFT_JIS", "ASCII");
290	if (cd == (iconv_t)-1)
291		return (-1);
292	if (iconvctl(cd, ICONV_SET_HOOKS, (void *)&hooks) != 0)
293		return (-1);
294	if (iconv(cd, inptr, &inbytesleft, outptr, &outbytesleft) == (size_t)-1)
295		return (-1);
296	if (iconv_close(cd) == -1)
297		return (-1);
298	return (wc_hook ? 0 : 1);
299}
300
301
302
303static int
304gnu_canonicalize1(void)
305{
306
307	return (strcmp(iconv_canonicalize("latin2"), "ISO-8859-2"));
308}
309
310static int
311gnu_canonicalize2(void)
312{
313
314	return (!strcmp(iconv_canonicalize("ASCII"), iconv_canonicalize("latin2")));
315}
316
317
318static int
319iconvlist_cb(unsigned int count, const char * const *names, void *data)
320{
321
322	return (*(int *)data = ((names == NULL) && (count > 0)) ? -1 : 0);
323}
324
325static int
326gnu_iconvlist(void)
327{
328	int i;
329
330	iconvlist(iconvlist_cb, (void *)&i);
331	return (i);
332}
333
334void
335mb_to_uc_fb(const char* inbuf, size_t inbufsize,
336    void (*write_replacement)(const unsigned int *buf, size_t buflen,
337       void* callback_arg), void* callback_arg, void* data)
338{
339	unsigned int c = 0x3F;
340
341	mb_uc_fb = true;
342	write_replacement((const unsigned int *)&c, 1, NULL);
343}
344
345static int __unused
346ctl_mb_to_uc_fb(void)
347{
348	struct iconv_fallbacks fb;
349	iconv_t cd;
350	size_t inbytesleft, outbytesleft;
351	uint16_t inbuf[1] = { 0xF187 };
352	uint8_t outbuf[4] = { 0x00, 0x00, 0x00, 0x00 };
353	const char *inptr;
354	char *outptr;
355	int ret;
356
357	if ((cd = iconv_open("UTF-32", "UTF-8")) == (iconv_t)-1)
358		return (1);
359
360	fb.uc_to_mb_fallback = NULL;
361	fb.mb_to_wc_fallback = NULL;
362	fb.wc_to_mb_fallback = NULL;
363	fb.mb_to_uc_fallback = mb_to_uc_fb;
364	fb.data = NULL;
365
366	if (iconvctl(cd, ICONV_SET_FALLBACKS, (void *)&fb) != 0)
367		return (1);
368
369	inptr = (const char *)inbuf;
370	outptr = (char *)outbuf;
371	inbytesleft = 2;
372	outbytesleft = 4;
373
374	errno = 0;
375	ret = iconv(cd, &inptr, &inbytesleft, &outptr, &outbytesleft);
376
377#ifdef VERBOSE
378	printf("mb_uc fallback: %c\n", outbuf[0]);
379#endif
380
381	if (mb_uc_fb && (outbuf[0] == 0x3F))
382		return (0);
383	else
384		return (1);
385}
386
387static int
388gnu_openinto(void)
389{
390	iconv_allocation_t *myspace;
391	size_t inbytesleft, outbytesleft;
392	const char *inptr;
393	char *inbuf = "works!", *outptr;
394	char outbuf[6];
395
396	if ((myspace = (iconv_allocation_t *)malloc(sizeof(iconv_allocation_t))) == NULL)
397		return (1);
398	if (iconv_open_into("ASCII", "ASCII", myspace) == -1)
399		return (1);
400
401	inptr = (const char *)inbuf;
402	outptr = (char *)outbuf;
403	inbytesleft = 6;
404	outbytesleft = 6;
405
406	iconv((iconv_t)myspace, &inptr, &inbytesleft, &outptr, &outbytesleft);
407
408	return ((memcmp(inbuf, outbuf, 6) == 0)	? 0 : 1);
409}
410
411static void
412test(int (tester) (void), const char * label)
413{
414	int ret;
415
416	if ((ret = tester()))
417		printf("%s failed (%d)\n", label, ret);
418	else
419		printf("%s succeeded\n", label);
420}
421
422int
423main(void)
424{
425	test(ctl_get_translit1, "ctl_get_translit1");
426	test(ctl_get_translit2, "ctl_get_translit2");
427	test(ctl_set_translit1, "ctl_set_translit1");
428	test(ctl_set_translit2, "ctl_set_translit2");
429	test(ctl_get_discard_ilseq1, "ctl_get_discard_ilseq1");
430	test(ctl_get_discard_ilseq2, "ctl_get_discard_ilseq2");
431	test(ctl_set_discard_ilseq1, "ctl_set_discard_ilseq1");
432	test(ctl_set_discard_ilseq2, "ctl_set_discard_ilseq2");
433	test(ctl_trivialp1, "ctl_trivialp1");
434	test(ctl_trivialp2, "ctl_trivialp2");
435	test(ctl_uc_hook, "ctl_uc_hook");
436	test(ctl_wc_hook, "ctl_wc_hook");
437//	test(ctl_mb_to_uc_fb, "ctl_mb_to_uc_fb");
438	test(gnu_openinto, "gnu_openinto");
439	test(gnu_canonicalize1, "gnu_canonicalize1");
440	test(gnu_canonicalize2, "gnu_canonicalize2");
441	test(gnu_iconvlist, "gnu_iconvlist");
442}
443