1/* $FreeBSD$ */
2/* $NetBSD: iconv.c,v 1.11 2009/03/03 16:22:33 explorer Exp $ */
3
4/*-
5 * Copyright (c) 2003 Citrus Project,
6 * Copyright (c) 2009, 2010 Gabor Kovesdan <gabor@FreeBSD.org>,
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32#include <sys/queue.h>
33#include <sys/types.h>
34
35#include <assert.h>
36#include <errno.h>
37#include <iconv.h>
38#include <limits.h>
39#include <paths.h>
40#include <stdbool.h>
41#include <stdlib.h>
42#include <string.h>
43
44#include "citrus_types.h"
45#include "citrus_module.h"
46#include "citrus_esdb.h"
47#include "citrus_hash.h"
48#include "citrus_iconv.h"
49
50#ifdef __weak_alias
51__weak_alias(libiconv, _iconv)
52__weak_alias(libiconv_open, _iconv_open)
53__weak_alias(libiconv_open_into, _iconv_open_into)
54__weak_alias(libiconv_close, _iconv_close)
55__weak_alias(libiconvlist, _iconvlist)
56__weak_alias(libiconvctl, _iconvctl)
57__weak_alias(libiconv_set_relocation_prefix, _iconv_set_relocation_prefix)
58__weak_alias(iconv_canonicalize, _iconv_canonicalize)
59#endif
60
61#define ISBADF(_h_)	(!(_h_) || (_h_) == (iconv_t)-1)
62
63int _libiconv_version = _LIBICONV_VERSION;
64
65iconv_t		 _iconv_open(const char *out, const char *in,
66		    struct _citrus_iconv *prealloc);
67
68iconv_t
69_iconv_open(const char *out, const char *in, struct _citrus_iconv *prealloc)
70{
71	struct _citrus_iconv *handle;
72	char *out_truncated, *p;
73	int ret;
74
75	handle = prealloc;
76
77	/*
78	 * Remove anything following a //, as these are options (like
79	 * //ignore, //translate, etc) and we just don't handle them.
80	 * This is for compatibilty with software that uses thees
81	 * blindly.
82	 */
83	out_truncated = strdup(out);
84	if (out_truncated == NULL) {
85		errno = ENOMEM;
86		return ((iconv_t)-1);
87	}
88
89	p = out_truncated;
90        while (*p != 0) {
91                if (p[0] == '/' && p[1] == '/') {
92                        *p = '\0';
93                        break;
94                }
95                p++;
96        }
97
98	ret = _citrus_iconv_open(&handle, in, out_truncated);
99	free(out_truncated);
100	if (ret) {
101		errno = ret == ENOENT ? EINVAL : ret;
102		return ((iconv_t)-1);
103	}
104
105	handle->cv_shared->ci_discard_ilseq = strcasestr(out, "//IGNORE");
106	handle->cv_shared->ci_hooks = NULL;
107
108	return ((iconv_t)(void *)handle);
109}
110
111iconv_t
112libiconv_open(const char *out, const char *in)
113{
114
115	return (_iconv_open(out, in, NULL));
116}
117
118int
119libiconv_open_into(const char *out, const char *in, iconv_allocation_t *ptr)
120{
121	struct _citrus_iconv *handle;
122
123	handle = (struct _citrus_iconv *)ptr;
124	return ((_iconv_open(out, in, handle) == (iconv_t)-1) ? -1 : 0);
125}
126
127int
128libiconv_close(iconv_t handle)
129{
130
131	if (ISBADF(handle)) {
132		errno = EBADF;
133		return (-1);
134	}
135
136	_citrus_iconv_close((struct _citrus_iconv *)(void *)handle);
137
138	return (0);
139}
140
141size_t
142libiconv(iconv_t handle, char **in, size_t *szin, char **out, size_t *szout)
143{
144	size_t ret;
145	int err;
146
147	if (ISBADF(handle)) {
148		errno = EBADF;
149		return ((size_t)-1);
150	}
151
152	err = _citrus_iconv_convert((struct _citrus_iconv *)(void *)handle,
153	    in, szin, out, szout, 0, &ret);
154	if (err) {
155		errno = err;
156		ret = (size_t)-1;
157	}
158
159	return (ret);
160}
161
162size_t
163__iconv(iconv_t handle, char **in, size_t *szin, char **out,
164    size_t *szout, uint32_t flags, size_t *invalids)
165{
166	size_t ret;
167	int err;
168
169	if (ISBADF(handle)) {
170		errno = EBADF;
171		return ((size_t)-1);
172	}
173
174	err = _citrus_iconv_convert((struct _citrus_iconv *)(void *)handle,
175	    in, szin, out, szout, flags, &ret);
176	if (invalids)
177		*invalids = ret;
178	if (err) {
179		errno = err;
180		ret = (size_t)-1;
181	}
182
183	return (ret);
184}
185
186int
187__iconv_get_list(char ***rlist, size_t *rsz, bool sorted)
188{
189	int ret;
190
191	ret = _citrus_esdb_get_list(rlist, rsz, sorted);
192	if (ret) {
193		errno = ret;
194		return (-1);
195	}
196
197	return (0);
198}
199
200void
201__iconv_free_list(char **list, size_t sz)
202{
203
204	_citrus_esdb_free_list(list, sz);
205}
206
207/*
208 * GNU-compatibile non-standard interfaces.
209 */
210static int
211qsort_helper(const void *first, const void *second)
212{
213	const char * const *s1;
214	const char * const *s2;
215
216	s1 = first;
217	s2 = second;
218	return (strcmp(*s1, *s2));
219}
220
221void
222libiconvlist(int (*do_one) (unsigned int, const char * const *,
223    void *), void *data)
224{
225	char **list, **names;
226	const char * const *np;
227	char *curitem, *curkey, *slashpos;
228	size_t sz;
229	unsigned int i, j;
230
231	i = 0;
232
233	if (__iconv_get_list(&list, &sz, true))
234		list = NULL;
235	qsort((void *)list, sz, sizeof(char *), qsort_helper);
236	while (i < sz) {
237		j = 0;
238		slashpos = strchr(list[i], '/');
239		curkey = (char *)malloc(slashpos - list[i] + 2);
240		names = (char **)malloc(sz * sizeof(char *));
241		if ((curkey == NULL) || (names == NULL)) {
242			__iconv_free_list(list, sz);
243			return;
244		}
245		strlcpy(curkey, list[i], slashpos - list[i] + 1);
246		names[j++] = strdup(curkey);
247		for (; (i < sz) && (memcmp(curkey, list[i], strlen(curkey)) == 0); i++) {
248			slashpos = strchr(list[i], '/');
249			curitem = (char *)malloc(strlen(slashpos) + 1);
250			if (curitem == NULL) {
251				__iconv_free_list(list, sz);
252				return;
253			}
254			strlcpy(curitem, &slashpos[1], strlen(slashpos) + 1);
255			if (strcmp(curkey, curitem) == 0) {
256				continue;
257			}
258			names[j++] = strdup(curitem);
259		}
260		np = (const char * const *)names;
261		do_one(j, np, data);
262		free(names);
263	}
264
265	__iconv_free_list(list, sz);
266}
267
268__inline const char
269*iconv_canonicalize(const char *name)
270{
271
272	return (_citrus_iconv_canonicalize(name));
273}
274
275int
276libiconvctl(iconv_t cd, int request, void *argument)
277{
278	struct _citrus_iconv *cv;
279	struct iconv_hooks *hooks;
280	const char *convname;
281	char src[PATH_MAX], *dst;
282	int *i;
283
284	cv = (struct _citrus_iconv *)(void *)cd;
285	hooks = (struct iconv_hooks *)argument;
286	i = (int *)argument;
287
288	if (ISBADF(cd)) {
289		errno = EBADF;
290		return (-1);
291	}
292
293	switch (request) {
294	case ICONV_TRIVIALP:
295		convname = cv->cv_shared->ci_convname;
296		dst = strchr(convname, '/');
297
298		strlcpy(src, convname, dst - convname + 1);
299		dst++;
300		if ((convname == NULL) || (src == NULL) || (dst == NULL))
301			return (-1);
302		*i = strcmp(src, dst) == 0 ? 1 : 0;
303		return (0);
304	case ICONV_GET_TRANSLITERATE:
305		*i = 1;
306		return (0);
307	case ICONV_SET_TRANSLITERATE:
308		return  ((*i == 1) ? 0 : -1);
309	case ICONV_GET_DISCARD_ILSEQ:
310		*i = cv->cv_shared->ci_discard_ilseq ? 1 : 0;
311		return (0);
312	case ICONV_SET_DISCARD_ILSEQ:
313		cv->cv_shared->ci_discard_ilseq = *i;
314		return (0);
315	case ICONV_SET_HOOKS:
316		cv->cv_shared->ci_hooks = hooks;
317		return (0);
318	case ICONV_SET_FALLBACKS:
319		errno = EOPNOTSUPP;
320		return (-1);
321	default:
322		errno = EINVAL;
323		return (-1);
324	}
325}
326
327void
328libiconv_set_relocation_prefix(const char *orig_prefix __unused,
329    const char *curr_prefix __unused)
330{
331
332}
333