77b82aa49f04063e91bde0c7652abea5138bcbc9
[lms] / lightmediascanner / src / lib / lightmediascanner_charset_conv.c
1 /**
2  * Copyright (C) 2007 by INdT
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  *
18  * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
19  */
20
21 #include "lightmediascanner_charset_conv.h"
22 #include <iconv.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27
28 struct lms_charset_conv {
29     iconv_t check;
30     iconv_t fallback;
31     unsigned int size;
32     iconv_t *convs;
33     char **names;
34 };
35
36 /**
37  * Create a new charset conversion tool.
38  *
39  * Conversion tool will try to convert provided strings to UTF-8, just need
40  * to register known charsets with lms_charset_conv_add() and then call
41  * lms_charset_conv().
42  *
43  * @return newly allocated conversion tool or NULL on error.
44  */
45 lms_charset_conv_t *
46 lms_charset_conv_new(void)
47 {
48     lms_charset_conv_t *lcc;
49
50     lcc = malloc(sizeof(*lcc));
51     if (!lcc) {
52         perror("malloc");
53         return NULL;
54     }
55
56     lcc->check = iconv_open("UTF-8", "UTF-8");
57     if (lcc->check == (iconv_t)-1) {
58         perror("ERROR: could not create conversion checker");
59         goto error_check;
60     }
61
62     lcc->fallback = iconv_open("UTF-8//IGNORE", "UTF-8");
63     if (lcc->fallback == (iconv_t)-1) {
64         perror("ERROR: could not create conversion fallback");
65         goto error_fallback;
66     }
67
68     lcc->size = 0;
69     lcc->convs = NULL;
70     lcc->names = NULL;
71     return lcc;
72
73   error_fallback:
74     iconv_close(lcc->check);
75   error_check:
76     free(lcc);
77
78     return NULL;
79 }
80
81 /**
82  * Free existing charset conversion tool.
83  *
84  * @param lcc existing Light Media Scanner charset conversion.
85  */
86 void
87 lms_charset_conv_free(lms_charset_conv_t *lcc)
88 {
89     int i;
90
91     if (!lcc)
92         return;
93
94     iconv_close(lcc->check);
95     iconv_close(lcc->fallback);
96
97     for (i = 0; i < lcc->size; i++) {
98         iconv_close(lcc->convs[i]);
99         free(lcc->names[i]);
100     }
101
102     if (lcc->convs)
103         free(lcc->convs);
104     if (lcc->names)
105         free(lcc->names);
106     free(lcc);
107 }
108
109 /**
110  * Register new charset to conversion tool.
111  *
112  * @param lcc existing Light Media Scanner charset conversion.
113  * @param charset charset name as understood by iconv_open(3).
114  *
115  * @return On success 0 is returned.
116  */
117 int
118 lms_charset_conv_add(lms_charset_conv_t *lcc, const char *charset)
119 {
120     iconv_t cd, *convs;
121     char **names;
122     int idx, ns;
123
124     if (!lcc)
125         return -1;
126
127     if (!charset)
128         return -2;
129
130     cd = iconv_open("UTF-8", charset);
131     if (cd == (iconv_t)-1) {
132         fprintf(stderr, "ERROR: could not add conversion charset '%s': %s\n",
133                 charset, strerror(errno));
134         return -3;
135     }
136
137     idx = lcc->size;
138     ns = lcc->size + 1;
139
140     convs = realloc(lcc->convs, ns * sizeof(*convs));
141     if (!convs)
142         goto realloc_error;
143     lcc->convs = convs;
144     lcc->convs[idx] = cd;
145
146     names = realloc(lcc->names, ns * sizeof(*names));
147     if (!names)
148         goto realloc_error;
149     lcc->names = names;
150     lcc->names[idx] = strdup(charset);
151     if (!lcc->names[idx])
152         goto realloc_error;
153
154     lcc->size = ns;
155     return 0;
156
157   realloc_error:
158     perror("realloc");
159     iconv_close(cd);
160     return -4;
161 }
162
163 static int
164 _find(const lms_charset_conv_t *lcc, const char *charset)
165 {
166     int i;
167
168     for (i = 0; i < lcc->size; i++)
169         if (strcmp(lcc->names[i], charset) == 0)
170             return i;
171
172     return -1;
173 }
174
175 /**
176  * Forget about previously registered charset in conversion tool.
177  *
178  * @param lcc existing Light Media Scanner charset conversion.
179  * @param charset charset name.
180  *
181  * @return On success 0 is returned.
182  */
183 int
184 lms_charset_conv_del(lms_charset_conv_t *lcc, const char *charset)
185 {
186     iconv_t *convs;
187     char **names;
188     int idx;
189
190     if (!lcc)
191         return -1;
192
193     if (!charset)
194         return -2;
195
196     idx = _find(lcc, charset);
197     if (idx < 0) {
198         fprintf(stderr, "ERROR: could not find charset '%s'\n", charset);
199         return -3;
200     }
201
202     iconv_close(lcc->convs[idx]);
203     free(lcc->names[idx]);
204
205     lcc->size--;
206     for (; idx < lcc->size; idx++) {
207         lcc->convs[idx] = lcc->convs[idx + 1];
208         lcc->names[idx] = lcc->names[idx + 1];
209     }
210
211     convs = realloc(lcc->convs, lcc->size * sizeof(*convs));
212     if (convs)
213         lcc->convs = convs;
214     else
215         perror("could not realloc 'convs'");
216
217     names = realloc(lcc->names, lcc->size * sizeof(*names));
218     if (names)
219         lcc->names = names;
220     else
221         perror("could not realloc 'names'");
222
223     return 0;
224 }
225
226 static int
227 _check(lms_charset_conv_t *lcc, const char *istr, unsigned int ilen, char *ostr, unsigned int olen)
228 {
229     char *inbuf, *outbuf;
230     size_t r, inlen, outlen;
231
232     inbuf = (char *)istr;
233     inlen = ilen;
234     outbuf = ostr;
235     outlen = olen;
236
237     iconv(lcc->check, NULL, NULL, NULL, NULL);
238     r = iconv(lcc->check, &inbuf, &inlen, &outbuf, &outlen);
239     if (r == (size_t)-1)
240         return -1;
241     else
242         return 0;
243 }
244
245 static int
246 _conv(iconv_t cd, char **p_str, unsigned int *p_len, char *ostr, unsigned int olen)
247 {
248     char *inbuf, *outbuf;
249     size_t r, inlen, outlen;
250
251     inbuf = *p_str;
252     inlen = *p_len;
253     outbuf = ostr;
254     outlen = olen;
255
256     iconv(cd, NULL, NULL, NULL, NULL);
257     r = iconv(cd, &inbuf, &inlen, &outbuf, &outlen);
258     if (r == (size_t)-1)
259         return -1;
260
261     *p_len = olen - outlen;
262     free(*p_str);
263     *p_str = ostr;
264
265     outbuf = realloc(*p_str, *p_len + 1);
266     if (!outbuf)
267         perror("realloc");
268     else
269         *p_str = outbuf;
270
271     (*p_str)[*p_len] = '\0';
272
273     return 0;
274 }
275
276 /**
277  * If required, do charset conversion to UTF-8.
278  *
279  * @param lcc existing Light Media Scanner charset conversion.
280  * @param p_str string to be converted.
281  * @param p_len string size.
282  *
283  * @return On success 0 is returned.
284  */
285 int
286 lms_charset_conv(lms_charset_conv_t *lcc, char **p_str, unsigned int *p_len)
287 {
288     char *outstr;
289     int i, outlen;
290
291     if (!lcc)
292         return -1;
293     if (!p_str)
294         return -2;
295     if (!p_len)
296         return -3;
297     if (!*p_str || !*p_len)
298         return 0;
299
300     outlen = 2 * *p_len;
301     outstr = malloc(outlen + 1);
302     if (!outstr) {
303         perror("malloc");
304         return -4;
305     }
306
307     if (_check(lcc, *p_str, *p_len, outstr, outlen) == 0) {
308         free(outstr);
309         return 0;
310     }
311
312     for (i = 0; i < lcc->size; i++)
313         if (_conv(lcc->convs[i], p_str, p_len, outstr, outlen) == 0)
314             return 0;
315
316     fprintf(stderr,
317             "WARNING: could not convert '%*s' to any charset, use fallback\n",
318             *p_len, *p_str);
319     i = _conv(lcc->fallback, p_str, p_len, outstr, outlen);
320     if (i < 0) {
321         memset(*p_str, '?', *p_len);
322         free(outstr);
323     }
324     return i;
325 }
326
327 /**
328  * Check if strings is not UTF-8 and conversion is required.
329  *
330  * @param lcc existing Light Media Scanner charset conversion.
331  * @param str string to be analysed.
332  * @param len string size.
333  *
334  * @return 0 if string is already UTF-8.
335  */
336 int
337 lms_charset_conv_check(lms_charset_conv_t *lcc, const char *str, unsigned int len)
338 {
339     char *outstr;
340     int r, outlen;
341
342     if (!lcc)
343         return -1;
344     if (!str || !len)
345         return 0;
346
347     outlen = 2 * len;
348     outstr = malloc(outlen);
349     if (!outstr) {
350         perror("malloc");
351         return -2;
352     }
353
354     r = _check(lcc, str, len, outstr, outlen);
355     free(outstr);
356     return r;
357 }