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