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