75c59796fad22a2d31838676c110f9f3e07115b4
[yaz-moved-to-github.git] / util / siconv.c
1 /*
2  * Copyright (c) 1997-2002, Index Data
3  * See the file LICENSE for details.
4  *
5  * $Id: siconv.c,v 1.7 2002-12-10 10:59:28 adam Exp $
6  */
7
8 /* mini iconv and wrapper for system iconv library (if present) */
9
10 #if HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13
14 #include <errno.h>
15 #include <string.h>
16 #include <ctype.h>
17
18 #if HAVE_ICONV_H
19 #include <iconv.h>
20 #endif
21
22 #include <yaz/yaz-util.h>
23
24 struct yaz_iconv_struct {
25     int my_errno;
26     int init_flag;
27     size_t (*init_handle)(yaz_iconv_t cd, unsigned char *inbuf,
28                           size_t inbytesleft, size_t *no_read);
29     unsigned long (*read_handle)(yaz_iconv_t cd, unsigned char *inbuf,
30                                  size_t inbytesleft, size_t *no_read);
31     size_t (*write_handle)(yaz_iconv_t cd, unsigned long x,
32                            char **outbuf, size_t *outbytesleft);
33 #if HAVE_ICONV_H
34     iconv_t iconv_cd;
35 #endif
36 };
37
38 static unsigned long yaz_read_ISO8859_1 (yaz_iconv_t cd, unsigned char *inp,
39                                          size_t inbytesleft, size_t *no_read)
40 {
41     unsigned long x = inp[0];
42     *no_read = 1;
43     return x;
44 }
45
46 static size_t yaz_init_UTF8 (yaz_iconv_t cd, unsigned char *inp,
47                              size_t inbytesleft, size_t *no_read)
48 {
49     if (inp[0] != 0xef)
50     {
51         *no_read = 0;
52         return 0;
53     }
54     if (inbytesleft < 3)
55     {
56         cd->my_errno = YAZ_ICONV_EINVAL;
57         return (size_t) -1;
58     }
59     if (inp[1] != 0xbb || inp[2] != 0xbf)
60     {
61         cd->my_errno = YAZ_ICONV_EILSEQ;
62         return (size_t) -1;
63     }
64     *no_read = 3;
65     return 0;
66 }
67
68 static unsigned long yaz_read_UTF8 (yaz_iconv_t cd, unsigned char *inp,
69                                     size_t inbytesleft, size_t *no_read)
70 {
71     unsigned long x = 0;
72
73     if (inp[0] <= 0x7f)
74     {
75         x = inp[0];
76         *no_read = 1;
77     }
78     else if (inp[0] <= 0xbf || inp[0] >= 0xfe)
79     {
80         *no_read = 0;
81         cd->my_errno = YAZ_ICONV_EILSEQ;
82     }
83     else if (inp[0] <= 0xdf && inbytesleft >= 2)
84     {
85         x = ((inp[0] & 0x1f) << 6) | (inp[1] & 0x3f);
86         if (x >= 0x80)
87             *no_read = 2;
88         else
89         {
90             *no_read = 0;
91             cd->my_errno = YAZ_ICONV_EILSEQ;
92         }
93     }
94     else if (inp[0] <= 0xef && inbytesleft >= 3)
95     {
96         x = ((inp[0] & 0x0f) << 12) | ((inp[1] & 0x3f) << 6) |
97             (inp[1] & 0x3f);
98         if (x >= 0x800)
99             *no_read = 3;
100         else
101         {
102             *no_read = 0;
103             cd->my_errno = YAZ_ICONV_EILSEQ;
104         }
105     }
106     else if (inp[0] <= 0xf7 && inbytesleft >= 4)
107     {
108         x =  ((inp[0] & 0x07) << 18) | ((inp[1] & 0x3f) << 12) |
109             ((inp[2] & 0x3f) << 6) | (inp[3] & 0x3f);
110         if (x >= 0x10000)
111             *no_read = 4;
112         else
113         {
114             *no_read = 0;
115             cd->my_errno = YAZ_ICONV_EILSEQ;
116         }
117     }
118     else if (inp[0] <= 0xfb && inbytesleft >= 5)
119     {
120         x =  ((inp[0] & 0x03) << 24) | ((inp[1] & 0x3f) << 18) |
121             ((inp[2] & 0x3f) << 12) | ((inp[3] & 0x3f) << 6) |
122             (inp[4] & 0x3f);
123         if (x >= 0x200000)
124             *no_read = 5;
125         else
126         {
127             *no_read = 0;
128             cd->my_errno = YAZ_ICONV_EILSEQ;
129         }
130     }
131     else if (inp[0] <= 0xfd && inbytesleft >= 6)
132     {
133         x =  ((inp[0] & 0x01) << 30) | ((inp[1] & 0x3f) << 24) |
134             ((inp[2] & 0x3f) << 18) | ((inp[3] & 0x3f) << 12) |
135             ((inp[4] & 0x3f) << 6) | (inp[5] & 0x3f);
136         if (x >= 0x4000000)
137             *no_read = 6;
138         else
139         {
140             *no_read = 0;
141             cd->my_errno = YAZ_ICONV_EILSEQ;
142         }
143     }
144     else
145     {
146         *no_read = 0;
147         cd->my_errno = YAZ_ICONV_EINVAL;
148     }
149     return x;
150 }
151
152 static unsigned long yaz_read_UCS4 (yaz_iconv_t cd, unsigned char *inp,
153                                     size_t inbytesleft, size_t *no_read)
154 {
155     unsigned long x = 0;
156     
157     if (inbytesleft < 4)
158     {
159         cd->my_errno = YAZ_ICONV_EINVAL; /* incomplete input */
160         *no_read = 0;
161     }
162     else
163     {
164         x = (inp[0]<<24) | (inp[1]<<16) | (inp[2]<<8) | inp[3];
165         *no_read = 4;
166     }
167     return x;
168 }
169
170 static unsigned long yaz_read_UCS4LE (yaz_iconv_t cd, unsigned char *inp,
171                                       size_t inbytesleft, size_t *no_read)
172 {
173     unsigned long x = 0;
174     
175     if (inbytesleft < 4)
176     {
177         cd->my_errno = YAZ_ICONV_EINVAL; /* incomplete input */
178         *no_read = 0;
179     }
180     else
181     {
182         x = (inp[3]<<24) | (inp[2]<<16) | (inp[1]<<8) | inp[0];
183         *no_read = 4;
184     }
185     return x;
186 }
187
188 static size_t yaz_write_UTF8 (yaz_iconv_t cd, unsigned long x,
189                               char **outbuf, size_t *outbytesleft)
190 {
191     unsigned char *outp = (unsigned char *) *outbuf;
192     if (x <= 0x7f && *outbytesleft >= 1)
193     {
194         *outp++ = (unsigned char) x;
195         (*outbytesleft)--;
196     } 
197     else if (x <= 0x7ff && *outbytesleft >= 2)
198     {
199         *outp++ = (unsigned char) ((x >> 6) | 0xc0);
200         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
201         (*outbytesleft) -= 2;
202     }
203     else if (x <= 0xffff && *outbytesleft >= 3)
204     {
205         *outp++ = (unsigned char) ((x >> 12) | 0xe0);
206         *outp++ = (unsigned char) (((x >> 6) & 0x3f) | 0x80);
207         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
208         (*outbytesleft) -= 3;
209     }
210     else if (x <= 0x1fffff && *outbytesleft >= 4)
211     {
212         *outp++ = (unsigned char) ((x >> 18) | 0xf0);
213         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
214         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
215         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
216         (*outbytesleft) -= 4;
217     }
218     else if (x <= 0x3ffffff && *outbytesleft >= 5)
219     {
220         *outp++ = (unsigned char) ((x >> 24) | 0xf8);
221         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
222         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
223         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
224         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
225         (*outbytesleft) -= 5;
226     }
227     else if (*outbytesleft >= 6)
228     {
229         *outp++ = (unsigned char) ((x >> 30) | 0xfc);
230         *outp++ = (unsigned char) (((x >> 24) & 0x3f) | 0x80);
231         *outp++ = (unsigned char) (((x >> 18) & 0x3f) | 0x80);
232         *outp++ = (unsigned char) (((x >> 12) & 0x3f) | 0x80);
233         *outp++ = (unsigned char) (((x >> 6)  & 0x3f) | 0x80);
234         *outp++ = (unsigned char) ((x & 0x3f) | 0x80);
235         (*outbytesleft) -= 6;
236     }
237     else 
238     {
239         cd->my_errno = YAZ_ICONV_E2BIG;  /* not room for output */
240         return (size_t)(-1);
241     }
242     *outbuf = (char *) outp;
243     return 0;
244 }
245
246 static size_t yaz_write_ISO8859_1 (yaz_iconv_t cd, unsigned long x,
247                                    char **outbuf, size_t *outbytesleft)
248 {
249     unsigned char *outp = (unsigned char *) *outbuf;
250     if (x > 255 || x < 1)
251     {
252         cd->my_errno = YAZ_ICONV_EILSEQ;
253         return (size_t) -1;
254     }
255     else if (*outbytesleft >= 1)
256     {
257         *outp++ = (unsigned char) x;
258         (*outbytesleft)--;
259     }
260     else 
261     {
262         cd->my_errno = YAZ_ICONV_E2BIG;
263         return (size_t)(-1);
264     }
265     *outbuf = (char *) outp;
266     return 0;
267 }
268
269
270 static size_t yaz_write_UCS4 (yaz_iconv_t cd, unsigned long x,
271                               char **outbuf, size_t *outbytesleft)
272 {
273     unsigned char *outp = (unsigned char *) *outbuf;
274     if (*outbytesleft >= 4)
275     {
276         *outp++ = (unsigned char) (x<<24);
277         *outp++ = (unsigned char) (x<<16);
278         *outp++ = (unsigned char) (x<<8);
279         *outp++ = (unsigned char) x;
280         (*outbytesleft) -= 4;
281     }
282     else
283     {
284         cd->my_errno = YAZ_ICONV_E2BIG;
285         return (size_t)(-1);
286     }
287     *outbuf = (char *) outp;
288     return 0;
289 }
290
291 static size_t yaz_write_UCS4LE (yaz_iconv_t cd, unsigned long x,
292                                 char **outbuf, size_t *outbytesleft)
293 {
294     unsigned char *outp = (unsigned char *) *outbuf;
295     if (*outbytesleft >= 4)
296     {
297         *outp++ = (unsigned char) x;
298         *outp++ = (unsigned char) (x<<8);
299         *outp++ = (unsigned char) (x<<16);
300         *outp++ = (unsigned char) (x<<24);
301         (*outbytesleft) -= 4;
302     }
303     else
304     {
305         cd->my_errno = YAZ_ICONV_E2BIG;
306         return (size_t)(-1);
307     }
308     *outbuf = (char *) outp;
309     return 0;
310 }
311
312 int yaz_iconv_isbuiltin(yaz_iconv_t cd)
313 {
314     return cd->read_handle && cd->write_handle;
315 }
316
317 yaz_iconv_t yaz_iconv_open (const char *tocode, const char *fromcode)
318 {
319     yaz_iconv_t cd = (yaz_iconv_t) xmalloc (sizeof(*cd));
320
321     cd->write_handle = 0;
322     cd->read_handle = 0;
323     cd->init_handle = 0;
324     cd->my_errno = YAZ_ICONV_UNKNOWN;
325
326     /* a useful hack: if fromcode has leading @,
327        the library not use YAZ's own conversions .. */
328     if (fromcode[0] == '@')
329         fromcode++;
330     else
331     {
332         if (!yaz_matchstr(fromcode, "UTF8"))
333         {
334             cd->read_handle = yaz_read_UTF8;
335             cd->init_handle = yaz_init_UTF8;
336         }
337         else if (!yaz_matchstr(fromcode, "ISO88591"))
338             cd->read_handle = yaz_read_ISO8859_1;
339         else if (!yaz_matchstr(fromcode, "UCS4"))
340             cd->read_handle = yaz_read_UCS4;
341         else if (!yaz_matchstr(fromcode, "UCS4LE"))
342             cd->read_handle = yaz_read_UCS4LE;
343         
344         if (!yaz_matchstr(tocode, "UTF8"))
345             cd->write_handle = yaz_write_UTF8;
346         else if (!yaz_matchstr(tocode, "ISO88591"))
347             cd->write_handle = yaz_write_ISO8859_1;
348         else if (!yaz_matchstr (tocode, "UCS4"))
349             cd->write_handle = yaz_write_UCS4;
350         else if (!yaz_matchstr(tocode, "UCS4LE"))
351             cd->write_handle = yaz_write_UCS4LE;
352     }
353 #if HAVE_ICONV_H
354     cd->iconv_cd = 0;
355     if (!cd->read_handle || !cd->write_handle)
356     {
357         cd->iconv_cd = iconv_open (tocode, fromcode);
358         if (cd->iconv_cd == (iconv_t) (-1))
359         {
360             xfree (cd);
361             return 0;
362         }
363     }
364 #else
365     if (!cd->read_handle || !cd->write_handle)
366     {
367         xfree (cd);
368         return 0;
369     }
370 #endif
371     cd->init_flag = 1;
372     return cd;
373 }
374
375 size_t yaz_iconv (yaz_iconv_t cd, char **inbuf, size_t *inbytesleft,
376                   char **outbuf, size_t *outbytesleft)
377 {
378     char *inbuf0;
379     size_t r = 0;
380 #if HAVE_ICONV_H
381     if (cd->iconv_cd)
382     {
383         size_t r =
384             iconv(cd->iconv_cd, inbuf, inbytesleft, outbuf, outbytesleft);
385         if (r == (size_t)(-1))
386         {
387             switch (yaz_errno())
388             {
389             case E2BIG:
390                 cd->my_errno = YAZ_ICONV_E2BIG;
391                 break;
392             case EINVAL:
393                 cd->my_errno = YAZ_ICONV_EINVAL;
394                 break;
395             case EILSEQ:
396                 cd->my_errno = YAZ_ICONV_EILSEQ;
397                 break;
398             default:
399                 cd->my_errno = YAZ_ICONV_UNKNOWN;
400             }
401         }
402         return r;
403     }
404 #endif
405     if (inbuf == 0 || *inbuf == 0)
406     {
407         cd->init_flag = 1;
408         cd->my_errno = YAZ_ICONV_UNKNOWN;
409         return 0;
410     }
411     inbuf0 = *inbuf;
412
413     if (cd->init_flag)
414     {
415         if (cd->init_handle)
416         {
417             size_t no_read;
418             size_t r = (cd->init_handle)(cd, (unsigned char *) *inbuf,
419                                          *inbytesleft, &no_read);
420             if (r)
421             {
422                 if (cd->my_errno == YAZ_ICONV_EINVAL)
423                     return r;
424                 cd->init_flag = 0;
425                 return r;
426             }
427             *inbytesleft -= no_read;
428             *inbuf += no_read;
429         }
430         cd->init_flag = 0;
431     }
432     while (1)
433     {
434         unsigned long x;
435         size_t no_read;
436
437         if (*inbytesleft == 0)
438         {
439             r = *inbuf - inbuf0;
440             break;
441         }
442         
443         x = (cd->read_handle)(cd, (unsigned char *) *inbuf, *inbytesleft,
444                               &no_read);
445         if (no_read == 0)
446         {
447             r = (size_t)(-1);
448             break;
449         }
450         r = (cd->write_handle)(cd, x, outbuf, outbytesleft);
451         if (r)
452             break;
453         *inbytesleft -= no_read;
454         (*inbuf) += no_read;
455     }
456     return r;
457 }
458
459 int yaz_iconv_error (yaz_iconv_t cd)
460 {
461     return cd->my_errno;
462 }
463
464 int yaz_iconv_close (yaz_iconv_t cd)
465 {
466 #if HAVE_ICONV_H
467     if (cd->iconv_cd)
468         iconv_close (cd->iconv_cd);
469 #endif
470     xfree (cd);
471     return 0;
472 }
473
474